GO语言中string和[]byte的区别及转换
区别在我们一样平常的开发中经常需要处理字符串,而在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 = 'H' // 编译错误:cannot assign to s(注:这里提一嘴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 = '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
Lenint
}
type SliceHeader struct {
Data uintptr
Lenint
Capint
}两种转换的性能对比
转换函数:
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 = 'H'
// str作为string不可修改,bs作为[]byte可修改,通过强转换二者指向同一个底层数组
// 修改bs时会出现严重错误,通过 defer + recover 也不能捕获
/*
unexpected fault address 0x1dc1f8
fatal error: fault
*/测试效果:
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企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]