区别
在我们一样平常的开发中经常需要处理字符串,而在GO语言中,字符串和[]byte是两种差别的类型。
- 首先来看string的底层界说(src/runtime/string.go):
- type stringStruct struct {
- str unsafe.Pointer
- len int
- }
复制代码
- []byte的底层界说(src/runtime/slice.go):
- type slice struct {
- array unsafe.Pointer
- len int
- cap int
- }
复制代码 二者都包含一个指向底层数组的指针,和底层数组的长度。差别点在于:
- string是不可变的,一旦创建就不能修改,因此适适用于只读场景;
- []byte是可变的,可以修改,且包含一个容量信息(cap);
(注:这里就不展开slice的扩容机制了,可以参考网上其他信息)
什么叫string是不可变的呢?举个例子:- s := "hello world"
- s[0] = 'H' // 编译错误:cannot assign to s[0]
复制代码 (注:这里提一嘴go语言中单引号用来表示byte类型,双引号用来表示string类型)
string不可变的含义是不能修改string底层数组的某个元素,但我们可以修改string引用的底层数组:- s := "hello world"
- s = "another string"
复制代码 这时间s的底层数组已经发生了变化,我们创建了一个新的底层数组(another string)并将s的指针指向它。原先的底层数组(hello world)将等待gc回收。
[]byte是可变的,我们可以修改它的元素:- b := []byte{'h', 'e', 'l', 'l', 'o'}
- b[0] = 'H'
复制代码 这时间b的底层数组中第一个元素已经变成了'H'。
转换
在实际使用时,我们可能需要将string与[]byte相互转换,有以下两种常见的方式:
平凡转换
string转[]byte:- s := "hello world"
- b := []byte(s)
复制代码 []byte转string:- b := []byte{'h', 'e', 'l', 'l', 'o'}
- s := string(b)
复制代码 强转换 (有风险 审慎使用)
- 在go版本=1.20中 由于安全性问题reflect包中的StringHeader和SliceHeader已被标注为deprecated,建议使用unsafe包来实现转换。
// Deprecated: Use unsafe.String or unsafe.StringData instead.
// Deprecated: Use unsafe.Slice or unsafe.SliceData instead.
- func String2Bytes(s string) []byte {
- sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
- bh := reflect.SliceHeader{
- Data: sh.Data,
- Len: sh.Len,
- Cap: sh.Len,
- }
- return *(*[]byte)(unsafe.Pointer(&bh))
- }
- func Bytes2String(b []byte) string {
- return *(*string)(unsafe.Pointer(&b))
- }
复制代码 注:强转换可能出现重大问题!!!如下:- type StringHeader struct {
- Data uintptr
- Len int
- }
- type SliceHeader struct {
- Data uintptr
- Len int
- Cap int
- }
复制代码 两种转换的性能对比
转换函数:- func String2Bytes(s string) []byte {
- // StringData获取string的底层数组指针,unsafe.Slice通过指针和长度构建切片
- return unsafe.Slice(unsafe.StringData(s), len(s))
- }
- func Bytes2String(b []byte) string {
- // SliceData获取切片的底层数组指针,unsafe.String通过指针和长度构建string
- return unsafe.String(unsafe.SliceData(b), len(b))
- }
复制代码 测试代码:- str := "hello world"
- bs := String2Bytes(str)
- bs[0] = 'H'
- // str作为string不可修改,bs作为[]byte可修改,通过强转换二者指向同一个底层数组
- // 修改bs时会出现严重错误,通过 defer + recover 也不能捕获
- /*
- unexpected fault address 0x1dc1f8
- fatal error: fault
- [signal 0xc0000005 code=0x1 addr=0x1dc1f8 pc=0x1a567b]
- */
复制代码 测试效果:- func String2Bytes(s string) []byte {
- return unsafe.Slice(unsafe.StringData(s), len(s))
- }
- func Bytes2String(b []byte) string {
- return unsafe.String(unsafe.SliceData(b), len(b))
- }
- func String2Bytes_basic(s string) []byte {
- return []byte(s)
- }
- func Bytes2String_basic(b []byte) string {
- return string(b)
- }
复制代码 显然使用强转换的性能更高,原因在于对于标准转换,无论是从 []byte 转 string 还是 string 转 []byte 都会涉及底层数组的拷贝。而强转换是直接替换指针的指向,从而使得 string 和 []byte 指向同一个底层数组。当数据长度大于 32 个字节时,标准转换需要通过 mallocgc 申请新的内存,之后再进行数据拷贝工作。所以,当转换数据较大时,两者性能差距会愈加明显。
两种转换方式的选择:
- 在你不确定安全隐患的条件下,尽量采用标准方式进行数据转换。
- 当程序对运行性能有高要求,同时满意对数据仅仅只有读操作的条件,且存在频繁转换(例如消息转发场景),可以使用强转换。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |