Golang中的字符串
Golang 中的string类型存储的字符串是不可变的, 如果要修改string内容需要将string转换为[]byte或[]rune,并且修改后的string内容是重新分配的, 原字符串将被gc回收;
package mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "strings")func main() { // 声明一个Buffer var buf strings.Builder buf.WriteString("good ") buf.WriteString("boy!") fmt.Println(buf.String()) // good boy!}