【Go底层】string和[]byte相互转换原理

1、背景

在写go代码的过程中,我们会经常遇到字节数组([]byte)和字符串(string)的转换,比如将[]byte转换为string:string(b);将string转换为[]byte:[]byte(s),这种转换并不是无损耗的,在数据量大的时候会在堆上申请一块新的内存去存储转换之后的数据,要对性能有要求的情况下就要考虑避免这种转换,接下来我们来看一下源码实现。

2、Go版本

$ go version
go version go1.21.4 windows/386

3、源码解释

【1】string转[]byte

[]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 //返回字节数组
}

这种转换方式将字符串底层数组的内容拷贝到另一块空间再返回,因为使用了新的空间地址并且涉及到了拷贝,是有一定性能损耗的。
也有方法直接将字符串的底层数组作为字节数组返回,不使用额外的内存空间和拷贝,但是这个操作比较危险,一旦修改字节数组的内容就会影响到字符串底层数组的内容,而字符串是不允许修改的。

【2】[]byte转string

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) //直接使用要转换的字节数组作为底层数组
}

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

4、总结

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

Go中,可以使用标准转换或强转换byte转换string。标准转换涉及底层数组的拷贝,而强转换则直接替换指针的指向,使得string[]byte指向同一个底层数组。因此,强转换的性能更好。 以下是使用标准转换转换byte转换string的示例代码: 标准转换: ``` b := []byte{104, 101, 108, 108, 111} // byte数组 s := string(b) // 转换string ``` 强转换: ``` b := []byte{104, 101, 108, 108, 111} // byte数组 s := *(*string)(unsafe.Pointer(&b)) // 强转换string ``` 请注意,强转换需要使用`unsafe`包,因此需要谨慎使用,并确保操作的安全性正确性。 参考资料: 对于标准转换,无论是从[]bytestring还是string[]byte都会涉及底层数组的拷贝。而强转换是直接替换指针的指向,从而使得string[]byte指向同一个底层数组。这样,当然后者的性能会更好。 golang []bytestring相互转换_墨痕诉清风的博客-CSDN博客_golang字符串byte数组<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Golang中[]bytestring转换全解析](https://blog.csdn.net/slphahaha/article/details/109405685)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [10-【gogolang []bytestring相互转换](https://blog.csdn.net/qq_42303254/article/details/116981159)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值