简单谈谈Golang中的字符串与字节数组

2020-01-28 13:55:17王振洲

类型转换

当我们使用 Go 语言做一些 JSON 等数据格式的解析和序列化时,可能经常会将这些变量在字符串和字节数组之间来回转换,类型之间转换的开销并没有想象的这么小,我们经常会看到 slicebytetostring 等函数出现在火焰图中,这个函数就是将字节数组转换成字符串所使用的函数,也就是一个类似 string(bytes) 的操作会在编译期间转换成 slicebytetostring 的函数调用,这个函数在函数体中首先会处理两种比较常见的情况,也就是字节长度为 0 或者 1 的情况:


func slicebytetostring(buf *tmpBuf, b []byte) (str string) {
 l := len(b)
 if l == 0 {
 return ""
 }
 if l == 1 {
 stringStructOf(&str).str = unsafe.Pointer(&staticbytes[b[0]])
 stringStructOf(&str).len = 1
 return
 }

 var p unsafe.Pointer
 if buf != nil && len(b) <= len(buf) {
 p = unsafe.Pointer(buf)
 } else {
 p = mallocgc(uintptr(len(b)), nil, false)
 }
 stringStructOf(&str).str = p
 stringStructOf(&str).len = len(b)
 memmove(p, (*(*slice)(unsafe.Pointer(&b))).array, uintptr(len(b)))
 return
}

处理过后会根据传入的缓冲区大小决定是否需要为新的字符串分配一片内存空间, stringStructOf 会将传入的字符串指针转换成 stringStruct 结构体指针,然后设置结构体持有的指针 str 和字符串长度 len ,最后通过 memmove 将原字节数组中的字节全部复制到新的内存空间中。

从字符串到字节数组的转换使用的就是 stringtoslicebyte 函数了,这个函数的实现非常简单:


func stringtoslicebyte(buf *tmpBuf, s string) []byte {
 var b []byte
 if buf != nil && len(s) <= len(buf) {
 *buf = tmpBuf{}
 b = buf[:len(s)]
 } else {
 b = rawbyteslice(len(s))
 }
 copy(b, s)
 return b
}

它会使用传入的缓冲区或者根据字符串的长度调用 rawbyteslice 创建一个新的字节切片, copy 关键字就会将字符串中的内容拷贝到新的字节数组中。

字符串和字节数组中的内容虽然一样,但是字符串的内容是只读的,我们不能通过下标或者其他形式改变其内存存储的数据,而字节切片中的内容都是可以读写的,所以无论是从哪种类型转换到另一种都需要对其中的内容进行拷贝,内存拷贝的性能损耗会随着字符串数组和字节长度的增长而增长,所以在做这种类型转换时一定要注意性能上的问题。