Golang中的字符串
Golang 中的string類型存儲的字符串是不可變的, 如果要修改string內容需要將string轉換為[]byte或[]rune,並且修改后的string內容是重新分配的, 原字符串將被gc回收;
package main
import (
"fmt"
)
func main() {
s := "hi, go"
fmt.Printf("value of str: %v\n", s)
fmt.Printf("ptr of str: %p\n", &s)
// 修改, 將,修改為!
bs := []byte(str)
bs[2] = '!'
fmt.Printf("value of lstr: %v\n", string(bs))
fmt.Printf("ptr of lstr: %p\n", &bs)
}
結果:
value of str: hi, go
ptr of str: 0xc00000e1f0
value of lstr: hi! go
ptr of lstr: 0xc00000a080
可以看到bs 和 s 的地址空間不同了,可見字符串的修改是會重新分配的;
Golang中string有2種類型, 只包含ASCII碼的string, 已經包含中文等其他復雜類型的string; 我們知道中文是占3個字節的;
其中:只包含ASCII碼的string的string 能通過索引的方式查找對應位置的字符;而包含中文的string類型rune,要想完整的顯示中文,需要使用for…range循環;
Golang字符串拼接方法
golang中要實現字符串的拼接,有很多種方法,最常見的當然是使用運算符"+"進行拼接了,還有很多其他的方法,下面依次介紹,並說明其優缺點.
直接使用運算符
// 不換行
str := "hello, " + "golang"
// 換行,換行是"+" 必須在上一行的結尾處
str1 := "The only person " +
"standing in your way " +
"is you"
上面提到golang里面的字符串都是不可變的,每次運算都會產生一個新的字符串.
所以使用運算符"+"連接字符串會產生很多臨時的無用的字符串,會給 gc 帶來額外的負擔,所以性能比較差.
使用fmt.Sprintf
fmt包是golang中的基礎包,fmt 包實現了格式化I/O函數,類似於C的 printf 和 scanf.
在fmt包中提供了一個方法func Sprintf(format string, a ...interface{}) string
,使用格式話的方式可以將多個字符串拼接到一起
str := fmt.Sprintf("%s %s %s", "format", "string", "by fmt.Sprintf")
這種方式,使用簡單,雖然不會像"+"連接那樣生成多余的string,但是內部實現頗為復雜,性能不是很好.
使用strings.Join
golang的 strings 包為字符串的拼接提供了一個方法func Join(a []string, sep string) string
, Join的內部實現比fmt.Sprintf要簡單的多,思路就是: Join會先根據字符串數組的內容,計算出一個拼接之后的長度,然后申請對應大小的內存,一個一個字符串填入.代碼如下:
// Join 將傳如的字符串連接成一個字符串
func Join(a []string, sep string) string {
// 如果字符串數量少,直接使用運算符拼接
switch len(a) {
case 0:
return ""
case 1:
return a[0]
case 2:
return a[0] + sep + a[1]
case 3:
return a[0] + sep + a[1] + sep + a[2]
}
// 計算最終字符串的字符大小
// 首先計算連接符sep的大小
n := len(sep) * (len(a) -1)
// 計算被連接的字符串的字符數
for _, value := range a {
n += len(value)
}
// 知道了總的字符數量,創建對應大小的數組
b := make([]byte, n)
bp := copy(b, a[0])
for _, s := range a[1:] {
bp += copy(b[bp:], sep)
bp += copy(b[bp:], s)
}
return string(b)
}
這種方式實現字符串的拼接,簡單方便,效率也是很高的,建議使用,唯一的不足就是在生成數組的時候開銷比較大;
使用bytes.Buffer
bytes包中的Buffer提供了一個方法 func (b *Buffer) WriteString(s string) (n int, err error)
WriteString將s的內容追加到緩沖區,並根據需要增加緩沖區。返回值n為s的長度;err總是nil。
如果緩沖區太大,WriteString將會因為ErrTooLarge而陷入恐慌。
package main
import (
"fmt"
"bytes"
)
func main() {
// 聲明一個Buffer
var buf bytes.Buffer
buf.WriteString("good ")
buf.WriteString("boy!")
fmt.Println(buf.String()) // good boy!
}
這個比較理想,可以當成可變字符使用,對內存的增長也有優化,如果能預估字符串的長度,還可以用 buffer.Grow() 接口來設置 capacity。
使用strings.Builder
strings.Builder 內部通過 slice 來保存和管理內容。slice 內部則是通過一個指針指向實際保存內容的數組。
strings.Builder 同樣也提供了 Grow() 來支持預定義容量。
當我們可以預定義我們需要使用的容量時,strings.Builder 就能避免擴容而創建新的 slice 了。strings.Builder是非線程安全,性能上和 bytes.Buffer 相差無幾。
package main
import (
"fmt"
"strings"
)
func main() {
// 聲明一個Buffer
var buf strings.Builder
buf.WriteString("good ")
buf.WriteString("boy!")
fmt.Println(buf.String()) // good boy!
}