实施是
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Run Code Online (Sandbox Code Playgroud)
根据我的测试,将 []byte 转换为字符串使用“写时复制”,或者编译器生成深复制指令,如果其中任何一个正在更改内部切片:
{
a := []byte{'a'}
s1 := string(a)
a[0] = 'b'
fmt.Println(s1) // a
}
{
a := "a"
b := []byte(a)
b[0] = 'b'
fmt.Println(a) // a
}
Run Code Online (Sandbox Code Playgroud)
那么如果按照下面的方式实现会发生什么?
// String returns the accumulated string.
func (b *Builder) String() string {
return string(b.buf)
}
Run Code Online (Sandbox Code Playgroud)
给定足够大的字符串,类型转换需要内存分配,而使用 unsafe 包的转换不需要:
package main
import (
"testing"
"unsafe"
)
func BenchmarkConversion(b *testing.B) {
buf := make([]byte, 16<<10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
var _ string = string(buf)
}
}
func BenchmarkUnsafe(b *testing.B) {
buf := make([]byte, 16<<10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
var _ string = *(*string)(unsafe.Pointer(&buf))
}
}
Run Code Online (Sandbox Code Playgroud)
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
BenchmarkConversion-8 307087 3897 ns/op 16384 B/op 1 allocs/op
BenchmarkUnsafe-8 1000000000 0.299 ns/op 0 B/op 0 allocs/op
PASS
ok _/tmp/tmp.KECLzZwkUn 1.579s
Run Code Online (Sandbox Code Playgroud)
您可以在strings.Builder此处查看有关引入api的更改列表的讨论:https : //go-review.googlesource.com/c/go/+/74931/4/src/strings/builder.go#30
正如您所期望的,这是对 API 机制、正确性和效率的讨论。
如果将代码替换为string(b.buf),则会生成构建字符串的副本。在将字节切片转换为字符串的简单情况下,编译器可能会优化掉副本,但编译器在这里通常不太可能这样做(因为它需要证明字符串生成器中的缓冲区是再也没有使用过)。
请注意,(标准库)代码看起来很危险,因为如果你这样写:
var b strings.Builder
b.WriteString("hello world")
c := b.String()
b.WriteString("a")
d := b.String()
Run Code Online (Sandbox Code Playgroud)
然后c和d将最终指向相同的内存。但这很好,因为字符串包含其缓冲区的长度。并且没有办法改变字符串,因为即使理论上支持字符串的内存可以通过bufin访问strings.Builder,但提供的唯一apis 附加到支持的内存。