Go底层之string和[]byte相互转换原理分析

作者:

文章目录
  • 在写go代码的过程中,我们会经常遇到字节数组([]byte)和字符串(string)的转换,比如将[]byte转换为string:string(b); 将string转换为[]byte:[]byte(s),这种转换并不是无损耗的,在数据量大的时候会在堆上申请一块新的内存去存储转换之后的数据,要对性能有要求的情况下就要考虑避免这种转换,接下来我们来看一下源码实现。
  • $ go version go version go1.21.4 windows/386
  • 字符串和字节数组转换虽然很简单,但是会带来一定的开销,当然并不是就不能使用这种转换方式,最主要还是根据我们的业务场景去使用,大部分业务场景性能要求并不是在这,但是我们也要尽量避免频繁的进行字符串和字节数组的相互转换。 以上为个人经验,希望能给大家一个参考,也希望大家多多支持风君子博客。 您可能感兴趣的文章: go语言中的Stringer的使用示例详解 Go中strings包的基本使用示例代码 Go语言字符串处理库strings包详解 Go语言中strings.HasPrefix、strings.Split、strings.SplitN() 函数 go中string、int、float相互转换方式
  • 目录
    • 背景
    • Go版本
    • 源码解释
      • 【1】string转[]byte
      • 【2】[]byte转string
    • 总结

      在写go代码的过程中,我们会经常遇到字节数组([]byte)和字符串(string)的转换,比如将[]byte转换为string:string(b);

      将string转换为[]byte:[]byte(s),这种转换并不是无损耗的,在数据量大的时候会在堆上申请一块新的内存去存储转换之后的数据,要对性能有要求的情况下就要考虑避免这种转换,接下来我们来看一下源码实现。

      $ go version
      go version go1.21.4 windows/386

      []byte(s)这种转换方式实际上用了标准库中的stringtoslicebyte函数,文件路径:src/runtime/string.go

      func stringtoslicebyte(buf *tmpBuf, s string) []byte {
      	var b []byte //定义一个字节数组
      	if buf != nil && len(s) <= len(buf) { //临时buf足够就使用临时buf作为返回的字节数组
      		*buf = tmpBuf{}
      		b = buf[:len(s)]
      	} else {  //临时buf不够就申请一块空间作为返回的字节数组
      		b = rawbyteslice(len(s))
      	}
      	copy(b, s) //将字符串底层数组的内容拷贝到字节数组中
      	return b //返回字节数组
      }
      

      这种转换方式将字符串底层数组的内容拷贝到另一块空间再返回,因为使用了新的空间地址并且涉及到了拷贝,是有一定性能损耗的。

      也有方法直接将字符串的底层数组作为字节数组返回,不使用额外的内存空间和拷贝,但是这个操作比较危险,一旦修改字节数组的内容就会影响到字符串底层数组的内容,而字符串是不允许修改的。

      string(b)这种转换方式用了标准库中的slicebytetostring函数,文件路径:src/runtime/string.go

      func slicebytetostring(buf *tmpBuf, ptr *byte, n int) string {
      	if n == 0 { //字节长度为0时,返回空字符串
      		return ""
      	}
      	
      	if n == 1 { //字节长度为1时
      		p := unsafe.Pointer(&staticuint64s[*ptr]) //得到指向字节数组地址对应的int64的值
      		if goarch.BigEndian { //是否为大端字节序
      			p = add(p, 7) //指针后移7字节
      		}
      		return unsafe.String((*byte)(p), 1) //返回单字节字符串
      	}
      
      	var p unsafe.Pointer
      	if buf != nil && n <= len(buf) { //字节数组长度小于临时buf长度直接使用临时buf作为字符串底层数组
      		p = unsafe.Pointer(buf)
      	} else { //字节数组长度大于临时buf长度在堆上新申请一块空间
      		p = mallocgc(uintptr(n), nil, false)
      	}
      	memmove(p, unsafe.Pointer(ptr), uintptr(n)) //将字节数组内容拷贝到新的地址p上
      	return unsafe.String((*byte)(p), n) //转换为string类型返回
      }
      

      这种转换方式将字节数组的内容拷贝到一个新的地址上做为字符串的底层数组,因为涉及到新的地址空间的申请和拷贝,所以是有一定开销的。

      标准库也提供了直接将要转换的字节数组作为字符串底层数组的方法,slicebytetostringtmp:

      func slicebytetostringtmp(ptr *byte, n int) string {
      	if raceenabled && n > 0 {
      		racereadrangepc(unsafe.Pointer(ptr),
      			uintptr(n),
      			getcallerpc(),
      			abi.FuncPCABIInternal(slicebytetostringtmp))
      	}
      	if msanenabled && n > 0 {
      		msanread(unsafe.Pointer(ptr), uintptr(n))
      	}
      	if asanenabled && n > 0 {
      		asanread(unsafe.Pointer(ptr), uintptr(n))
      	}
      	return unsafe.String(ptr, n) //直接使用要转换的字节数组作为底层数组
      }
      

      同样如果修改了字节数组的内容,字符串也会被影响,不推荐使用。

      字符串和字节数组转换虽然很简单,但是会带来一定的开销,当然并不是就不能使用这种转换方式,最主要还是根据我们的业务场景去使用,大部分业务场景性能要求并不是在这,但是我们也要尽量避免频繁的进行字符串和字节数组的相互转换。

      以上为个人经验,希望能给大家一个参考,也希望大家多多支持风君子博客。

      您可能感兴趣的文章:

      • go语言中的Stringer的使用示例详解
      • Go中strings包的基本使用示例代码
      • Go语言字符串处理库strings包详解
      • Go语言中strings.HasPrefix、strings.Split、strings.SplitN() 函数
      • go中string、int、float相互转换方式

      站内搜索