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

4

该实现是:

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

根据我的测试,将 []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
    }

如果按照以下方式实现会发生什么情况?

// String returns the accumulated string.
func (b *Builder) String() string {
    return string(b.buf)
}
2个回答

10
您可以在此处查看介绍 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()

那么cd最终将指向同一块内存。但没关系,因为字符串包含其缓冲区长度信息。而且无法更改字符串,因为即使在理论上可以通过strings.Builder中的buf访问支持字符串的内存,但提供的唯一API是向后面的内存添加内容。


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

$ 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

1
Go语言是由谷歌开发的,因此您可以期望它被优化为适用于谷歌类型环境。例如,具有大量内存和高可靠性要求的快速服务器。因此,在“资源与安全性”冲突中,他们总是默认选择安全性。尽管golang的工作人员会提供一种不安全但快速的选项,以防您需要使用。 - James Anderson
虽然String()方法使用了“unsafe”包,但该方法是安全的。该方法假设了字符串和切片的内存布局。该假设在今天是有效的,并且我们可以期望Go团队在假设失效时更新String()方法的实现。 - Charlie Tumahai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接