为什么golang strings.Builder 是这样实现String()的?

Moo*_*oon 4 go

实施是

// 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)

Pet*_*ter 9

给定足够大的字符串,类型转换需要内存分配,而使用 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)


Pau*_*kin 9

您可以在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)

然后cd将最终指向相同的内存。但这很好,因为字符串包含其缓冲区的长度。并且没有办法改变字符串,因为即使理论上支持字符串的内存可以通过bufin访问strings.Builder,但提供的唯一apis 附加到支持的内存。