[Golang] string vs. []byte 底层实现与性能分析
string和[]byte之间可以互相通过 s = string(bs)
, bs = []byte(s)
这样的语法强制转换类型,标准库的strings
和bytes
两个包内的函数能提供的能力也几乎一模一样。但是诛如compress
, io
这样的包里所用的变量总是[]byte
。那两者之间究竟有何异同?
底层实现
[]byte
首先我们都知道slice
和array
之间的关系。一个array
是不可变的,slice相当于是一个指向array上某一部分的指针。slice的底层实现如下
type slice struct {
array unsafe.Pointer
len int
cap int
}
array: 指向实际array
的指针
len, cap: 当前slice的长度, 当前slice的最大长度。
上述是slice与array的基本知识,更详细的讲解可自行搜索相关资料。
一个[]byte
自然也没有什么特殊的, 也是这样的一个slice
结构, 其中的array
指向一个byte array
。
由上述结构我们也可知,len
和cap
都是预先计算好的,另外由于golang在编译时会进行inline优化,len(ss)
这样的调用会被内联掉,实际上连一次函数调用的成本都没有, 就像直接读取了 slice.len
一样。因此对一个slice
调用 len(ss)
, cap(ss)
是个性能非常高的操作。 有些人担心多次对同一个slice调用len()
会性能差,有了如下这种写法,实在没什么必要:
data := []int{....}
lenOfData := len(data)
print(lenOfData)
n := lenOfData * 10
n2 := lenOfData + 10
....
string
string的底层结构如下:
type strStruct struct {
str unsafe.Pointer
len int
}
str
是指向一个[]byte
的指针, len
表示当前string的长度(可见len(s)
也是性能非常高的操作)。
异同
类型转换
根据底层实现,两者的关系如下图所示:
或许你有看过网上的**[]byte
强转 string
的奇技淫巧**s := *(*string)(unsafe.Pointer(&b))
, 比标准库的 s := string(b)
性能高得多,其原理就来自于此。
为什么Golang标准库还提供另一种慢得多的方法呢?因为在Golang的设计里, **string
是不可变的**。我们对一个string
进行拼接操作,实际上是复制了一份新的string
。 而使用这个方法,就会把string
底层的[]byte
暴露出来,破坏了“string不可变”的约定。
b := []byte("123456")
s := *(*string)(unsafe.Pointer(&b))
b[0] = 'a'
// 这时s变成了 "a23456
性能
“既然两者能花式互转,那我不在乎他俩有什么区别,只关心谁快”
别急,即使不对每个操作进行benchmark, 从上面的底层实现中,我们已经可以推理得到两者在各种操作下的性能差异了。
从上面的实现可知,对string的所有操作,最终都是操作在其底层的byte slice
中。因此,我们得到这两个结论:
- 纯查询检索类的操作,比如
Index
,LastIndex
,Contains
, 两者的性能几乎一样,string
略慢一点点,因为要多一层指针。 - 会改变内容的操作,比如
Replace
, 拼接,bytes
要明显快。因为string是不可变的(前文提过),进行此类操作时总是需要复制内容到一个新变量中。
也要注意一个小例外:
strings.Index(s, subs)
和 bytes.Index(b, []byte(subs))
谁快? 这时前者要快一点了。因为后者多出一个 []byte(s)
的操作。
使用总结
- 从
http
,file
,buffer
,reader
等处读取到的原始数据往往是[]byte
的。它更符合字节的语义的同时,能提供更好的性能。 - 从上面拿到原始的
[]byte
后不要急着第一时间就转string
, 先将各种必要的操作都做完, 要输出给人看,或者需要作为string
传参时,再转也不迟 - 如果第一时间拿到的变量就是
string
, 是否要转成[]byte
再进行后续各种操作则需要进行一些权衡。 - 如果系统不在意这点性能(99%的系统可能都是),或者有一些团队内部规范,不需要考虑这点差异。
转载自:https://juejin.cn/post/7272282550860644404