Go Unsafe String And StringData explained

Last Modified: 2023/08/11

概述

本文将会将会介绍 unsafe package 中的几个函数 String/StringData/Slice/SliceData,并介绍如何借助这些函数实现 byte slice 和 string 之间的高效转换。

在此之前

日常工作中,string 和 byte slice 之间的转换还是比较常见的,大部分情况下只需要使用强制类型转换即可:

s := []byte("hello,lucy")
str := string(s)
s1 := []byte(str)

这种转换很安全很直接,唯一的一个小缺点是效率不够高,因为这种方式的转换涉及到内存拷贝,为了追求极致性能,有人使用 unsafe 包 hack 了下面的转换方法:

func toBytes(s string) []byte {
    return *(*[]byte)(unsafe.Pointer(&s))
}
func toString(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

这种方式,避免了内存拷贝,string 和 byte slice 共享内存。toBytes 将 string 转换为 byte slice,返回的 byte slice 不可修改,否则会 panic。这很好理解,假如可以修改返回的 byte slice,那么相当于修改了 string,因为他们共享内存,这就违反了 string 不可变的语义。

toString 将 byte slice 转换为 string,同理 byte slice 转化为 string 之后,不应该修改该 byte slice,如果修改了 byte slice,string 也跟着被修改了,这违反了 string 不可变的语义。注意这里的用词是不应该,而不是不可以,即便你真的修改了,也不会报错。不过这种 hack 的做法,就别指望兼容性和可靠性了,可能将来的某个版本就 G 了。

s := []byte("hello,lucy")
str := toString(s)
println(str) // hello,lucy
s[0] = 'x'
println(str) // xello,lucy

从上面的输出可以看出,虽然 string 在语义上是不可变的,但是我们却通过改变 slice 改变了 string。

当有了这些函数之后

go1.20 引入了以下函数:

func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte
func SliceData(slice []ArbitraryType) *ArbitraryType

配合 go1.17 中引入的 Slice 函数:

func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

现在我们不必 hack 就可以实现 string 和 []byte 之间的高效转换,方法如下:

func StringToBytes(s string) []byte {
    return unsafe.Slice(unsafe.StringData(s), len(s))
}
func Bytes2String(s []byte) string {
    return unsafe.String(unsafe.SliceData(s), len(s))
}
有问题吗?点此反馈!

温馨提示:反馈需要登录