Published on

go字符串和字节数组的转换

Authors
  • avatar
    Name
    yushenw
    Linkedin

字符串和字节切片

Go 中的字符串是不可变的,这意味着一旦一个字符串被创建,它包含的字节序列就不能被改变。在底层,一个字符串通常由两部分组成:一个指向底层数组的指针和一个表示字符串长度的整数。 字节切片底层也是字节序列,但它们是完全不同的两种类型,内部表示也不同。所以将字节切片([]byte)转换为字符串(string)时,通常会发生内存的重新分配。

字符串和字节切片之间的转换

虽然字符串和字节切片在底层都是字节序列,但由于字符串的不可变性,它们之间的转换需要复制底层的数据:

  • 将字符串转换为字节切片([]byte(str)) 会创建一个新的字节切片,并将字符串的内容复制到这个新切片中。
  • 将字节切片转换为字符串(string(bytes)) 也会创建一个新的字符串,并将字节切片的内容复制到这个新字符串中。
byteSlice := []byte{104, 101, 108, 108, 111} // byte slice containing "hello"
str := string(byteSlice)                     // 创建一个新的字符串 "hello"

string(byteSlice) 转换会导致内存的重新分配,因为 Go 需要创建一个新的字符串对象来保持字符串的不可变性。在性能敏感的应用中,这种转换的成本是需要考虑的

一点转换的小技巧

strings.Builder在字符串拼接中有很高的效率,不仅仅是它预分配了内存,而且在从字节序列转换成字符串的时候使用了一点技巧:

func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

这段代码获取了buf的地址成unsafe.Pointer类型,这是一个通用的指针类型,允许任意类型的指针转换和比较。 然后将unsafe.Pointer类型转换为string类型指针,最后解引用这个指针及得到字符串对象的值。

转换风险

这种转换是非常高效的,因为它避免了从 strings.Builder 的内部缓冲区到字符串的内存拷贝。但是,它使用了 unsafe 包,这意味着标准的类型安全保证不再适用。这种做法在内部实现中是可行的,因为 Go 的运行时和标准库的开发者清楚地了解底层的内存布局和行为。

但在您自己的代码中使用 unsafe 包应当非常小心,因为不正确的使用可能导致内存安全问题,比如越界访问、数据竞争或其他类型的未定义行为。