美文网首页
关于Go的四种字符串拼接及性能比较'

关于Go的四种字符串拼接及性能比较'

作者: leejnull | 来源:发表于2020-01-08 22:29 被阅读0次

    先上代码

    // fmt.Sprintf
    func BenchmarkStringSprintf(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            var str string
            for j := 0; j < numbers; j++ {
                str = fmt.Sprintf("%s%d", str, j)
            }
        }
        b.StopTimer()
    }
    
    // add
    func BenchmarkStringAdd(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            var str string
            for j := 0; j < numbers; j++ {
                str = str + string(j)
            }
        }
        b.StopTimer()
    }
    
    // bytes.Buffer
    func BenchmarkStringBuffer(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            var buffer bytes.Buffer
            for j := 0; j < numbers; j++ {
                buffer.WriteString(strconv.Itoa(j))
            }
            _ = buffer.String()
        }
        b.StopTimer()
    }
    
    // strings.Builder
    func BenchmarkStringBuilder(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            var builder strings.Builder
            for j := 0; j < numbers; j++ {
                builder.WriteString(strconv.Itoa(j))
            }
            _ = builder.String()
        }
        b.StopTimer()
    }
    

    运行结果

    lijun:benchmark/ $ go test -bench=.                                                           [10:01:20]
    goos: darwin
    goarch: amd64
    pkg: class12/benchmark
    BenchmarkStringSprintf-4              30          47358694 ns/op
    BenchmarkStringAdd-4                  50          27664814 ns/op
    BenchmarkStringBuffer-4            10000            184422 ns/op
    BenchmarkStringBuilder-4           10000            157039 ns/op
    PASS
    ok      class12/benchmark       6.350s
    lijun:benchmark/ $                                                                            [10:01:58]
    

    如果还不知道 Go 的 benchmark,可以先去了解一下,个人认为还是非常不错的性能测试的工具。

    得出结论

    四种拼接字符串的方式,性能比较结果
    strings.Builder > bytes.Buffer > string add > fmt.Sprintf

    为什么?

    这里我们还是直接看源码吧

    先看 Sprintf
    // Sprintf formats according to a format specifier and returns the resulting string.
    func Sprintf(format string, a ...interface{}) string {
        p := newPrinter()
        p.doPrintf(format, a)
        s := string(p.buf)
        p.free()
        return s
    }
    

    这是 fmt.Sprintf 的源码,我们可以看到内部会通过 newPrinter 创建一个新对象 p,点进去看一下 newPrinter 这个函数

    // newPrinter allocates a new pp struct or grabs a cached one.
    func newPrinter() *pp {
        p := ppFree.Get().(*pp)
        p.panicking = false
        p.erroring = false
        p.fmt.init(&p.buf)
        return p
    }
    

    它会从系统的临时对象池中那 pp 这个对象,关于临时对象池(sync.Pool),下次有机会再探讨。这里可以知道, Sprintf 会从临时对象池中获取一个 *pp 的指针,然后再做一些格式化的操作,doPrintf 代码就不贴了,格式化后的底层字节会放到 []byte 这个切片里面,最后再 string 转换成字符串返回,并且释放掉 p 对象。
    整个过程:创建对象 - 格式化操作 - string化 - 释放对象

    接下来看 string

    string 是在 Go 里面是一个不可变类型,所以下面的代码

    str = str + str2
    

    每次都会创建一个新的 string 类型的值,然后重新赋值给 str 这个变量,相比于上面的 Sprintf 主要少了格式化这个过程,所以在性能上肯定要优于 Sprintf

    bytes.Buffer

    我们看一下 builder 的 String() 函数源码

    // String returns the contents of the unread portion of the buffer
    // as a string. If the Buffer is a nil pointer, it returns "<nil>".
    //
    // To build strings more efficiently, see the strings.Builder type.
    func (b *Buffer) String() string {
        if b == nil {
            // Special case, useful in debugging.
            return "<nil>"
        }
        return string(b.buf[b.off:])
    }
    

    字符串的底层结构是一个 []byte 的字节序列,而 Buffer 是直接获取未读取的 []byte序列,在转成 string 返回,少了重复创建对象这个步骤。
    b.buf 是 []byte 切片
    b.off 是已读取的字节位置

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

    strings.Builder 直接通过指针来操作了,在效率上更进一步。

    总结

    通过源码来分析,还是比较清晰明了的,但是限于我自身的水平,对于源码的解读并不都是特别深入,这里也是给大家做出一个参考。关于最后的通过转换成指针来返回字符串的操作,我也就是知道转成指针效率会高,但是关于为什么,也都是模棱两可(是因为直接通过操作内存地址吗)。总之关于基础性、底层的东西还是要多多学习。

    来自 https://leejnull.github.io/2019/08/29/2019-08-29/

    相关文章

      网友评论

          本文标题:关于Go的四种字符串拼接及性能比较'

          本文链接:https://www.haomeiwen.com/subject/rsreactx.html