【问题标题】:Why does golang strings.Builder implements String() like this?为什么golang strings.Builder会这样实现String()?
【发布时间】:2020-05-26 17:25:21
【问题描述】:

实现是

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

【问题讨论】:

    标签: go


    【解决方案1】:

    您可以在此处查看关于引入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 附加到支持的内存。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • Go 是在 google 开发的,因此您可以期望它针对 Google 类型的环境进行了优化。 IE。具有大量内存和高可靠性要求的快速服务器。因此,在“资源与安全”冲突中,他们将始终默认安全。虽然 golang 的好人确实会在您觉得需要时为您提供一个不安全但快速的选择。
      • @JamesAnderson 虽然 String() 方法使用了unsafe 包,但该方法使用起来是安全的。该方法对字符串和切片的内存布局进行了假设。该假设今天有效,如果 Go 团队使假设无效,我们可以期待 Go 团队更新 String() 方法实现。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-15
      相关资源
      最近更新 更多