GO语言中string和[]byte的区别及转换

打印 上一主题 下一主题

主题 888|帖子 888|积分 2664

区别

在我们一样平常的开发中经常需要处理字符串,而在GO语言中,字符串和[]byte是两种差别的类型。

  • 首先来看string的底层界说(src/runtime/string.go):
  1. type stringStruct struct {
  2.         str unsafe.Pointer
  3.         len int
  4. }
复制代码

  • []byte的底层界说(src/runtime/slice.go):
  1. type slice struct {
  2.         array unsafe.Pointer
  3.         len   int
  4.         cap   int
  5. }
复制代码
二者都包含一个指向底层数组的指针,和底层数组的长度。差别点在于:

  • string是不可变的,一旦创建就不能修改,因此适适用于只读场景;
  • []byte是可变的,可以修改,且包含一个容量信息(cap);
(注:这里就不展开slice的扩容机制了,可以参考网上其他信息)
什么叫string是不可变的呢?举个例子:
  1. s := "hello world"
  2. s[0] = 'H' // 编译错误:cannot assign to s[0]
复制代码
(注:这里提一嘴go语言中单引号用来表示byte类型,双引号用来表示string类型)
string不可变的含义是不能修改string底层数组的某个元素,但我们可以修改string引用的底层数组:
  1. s := "hello world"
  2. s = "another string"
复制代码
这时间s的底层数组已经发生了变化,我们创建了一个新的底层数组(another string)并将s的指针指向它。原先的底层数组(hello world)将等待gc回收。
[]byte是可变的,我们可以修改它的元素:
  1. b := []byte{'h', 'e', 'l', 'l', 'o'}
  2. b[0] = 'H'
复制代码
这时间b的底层数组中第一个元素已经变成了'H'。
转换

在实际使用时,我们可能需要将string与[]byte相互转换,有以下两种常见的方式:
平凡转换

string转[]byte:
  1. s := "hello world"
  2. b := []byte(s)
复制代码
[]byte转string:
  1. b := []byte{'h', 'e', 'l', 'l', 'o'}
  2. 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.
  1. func String2Bytes(s string) []byte {
  2.     sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
  3.     bh := reflect.SliceHeader{
  4.         Data: sh.Data,
  5.         Len:  sh.Len,
  6.         Cap:  sh.Len,
  7.     }
  8.     return *(*[]byte)(unsafe.Pointer(&bh))
  9. }
  10. func Bytes2String(b []byte) string {
  11.     return *(*string)(unsafe.Pointer(&b))
  12. }
复制代码
注:强转换可能出现重大问题!!!如下:
  1. type StringHeader struct {
  2.         Data uintptr
  3.         Len  int
  4. }
  5. type SliceHeader struct {
  6.         Data uintptr
  7.         Len  int
  8.         Cap  int
  9. }
复制代码
两种转换的性能对比

转换函数:
  1. func String2Bytes(s string) []byte {
  2.         // StringData获取string的底层数组指针,unsafe.Slice通过指针和长度构建切片
  3.         return unsafe.Slice(unsafe.StringData(s), len(s))
  4. }
  5. func Bytes2String(b []byte) string {
  6.         // SliceData获取切片的底层数组指针,unsafe.String通过指针和长度构建string
  7.         return unsafe.String(unsafe.SliceData(b), len(b))
  8. }
复制代码
测试代码:
  1. str := "hello world"
  2. bs := String2Bytes(str)
  3. bs[0] = 'H'
  4. // str作为string不可修改,bs作为[]byte可修改,通过强转换二者指向同一个底层数组
  5. // 修改bs时会出现严重错误,通过 defer + recover 也不能捕获
  6. /*
  7. unexpected fault address 0x1dc1f8
  8. fatal error: fault
  9. [signal 0xc0000005 code=0x1 addr=0x1dc1f8 pc=0x1a567b]
  10. */
复制代码
测试效果:
  1. func String2Bytes(s string) []byte {
  2.         return unsafe.Slice(unsafe.StringData(s), len(s))
  3. }
  4. func Bytes2String(b []byte) string {
  5.         return unsafe.String(unsafe.SliceData(b), len(b))
  6. }
  7. func String2Bytes_basic(s string) []byte {
  8.         return []byte(s)
  9. }
  10. func Bytes2String_basic(b []byte) string {
  11.         return string(b)
  12. }
复制代码
显然使用强转换的性能更高,原因在于对于标准转换,无论是从 []byte 转 string 还是 string 转 []byte 都会涉及底层数组的拷贝。而强转换是直接替换指针的指向,从而使得 string 和 []byte 指向同一个底层数组。当数据长度大于 32 个字节时,标准转换需要通过 mallocgc 申请新的内存,之后再进行数据拷贝工作。所以,当转换数据较大时,两者性能差距会愈加明显。
两种转换方式的选择:


  • 在你不确定安全隐患的条件下,尽量采用标准方式进行数据转换。
  • 当程序对运行性能有高要求,同时满意对数据仅仅只有读操作的条件,且存在频繁转换(例如消息转发场景),可以使用强转换。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

缠丝猫

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表