目录

golang字符串拼接大比拼

拼接字符串大对比

直接+拼接

func StringsAdd() string {
    var s string
    for _, v := range StrData {
        s += v
    }
    return s
}

使用fmt包进行组装

func StringsFmt() string {
    var s string = fmt.Sprint(StrData)
    return s
}

使用strings包的join方法

func StringsJoin() string {
    var s string = strings.Join(StrData, "")
    return s
}

strings.Join方法是实现效果最好的方法,耗时是最低的,内存占用也最低,额外内存分配次数也只有1次,我们查看strings.Join的方法内部的实现代码。

func Join(elems []string, sep string) string {
    switch len(elems) {
    case 0:
        return ""
    case 1:
        return elems[0]
    }
    n := len(sep) * (len(elems) - 1)
    for i := 0; i < len(elems); i++ {
        n += len(elems[i])
    }

    var b Builder
    b.Grow(n)
    b.WriteString(elems[0])
    for _, s := range elems[1:] {
        b.WriteString(sep)
        b.WriteString(s)
    }
    return b.String()
}

使用bytes.Buffer拼接

func StringsBuffer() string {
    var s bytes.Buffer
    for _, v := range StrData {
        s.WriteString(v)
    }
    return s.String()
}

使用strings.Builder拼接

func StringsBuilder() string {
    var b strings.Builder
    for _, v := range StrData {
        b.WriteString(v)
    }
    return b.String()
}

来看下builder内部细节

type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}

func (b *Builder) copyCheck() {
    if b.addr == nil {
        // This hack works around a failing of Go's escape analysis
        // that was causing b to escape and be heap allocated.
        // See issue 23382.
        // TODO: once issue 7921 is fixed, this should be reverted to
        // just "b.addr = b".
        b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
    } else if b.addr != b {
        panic("strings: illegal use of non-zero Builder copied by value")
    }
}

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


// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
    b.copyCheck()
    b.buf = append(b.buf, s...)
    return len(s), nil
}

// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
func (b *Builder) grow(n int) {
    buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
    copy(buf, b.buf)
    b.buf = buf
}

+拼接会造成大量的临时字符串,效率最低。 fmt.Sprintf内部的逻辑比较复杂,有很多额外的判断,还用到了 interface,所以性能也不是很好 bytes.buffer有缓冲,效果好一点,如果能预估字符串的长度,还可以用 buffer.Grow() 接口来设置 capacity strings.join内部其实使用的strings.builder,并且对builder使用了grow(n)方法,效率比较高 在Go 1.10开始,Go官方将strings.Builder作为一个feature引入,其能较大程度的提高字符串拼接的效率,为了解决bytes.Buffer.String()存在的[]byte -> string类型转换和内存拷贝问题,这里使用了一个unsafe.Pointer的内存指针转换操作,实现了直接将buf []byte转换为 string类型,同时避免了内存充分配的问题。如果我们也像strings.join那样使用 b.Grow(n)方法,效率也很快

总结

官方是建议使用strings.Builder的方式,其实strings.join也是使用的它,如果提前知道总字节的大小,设置容量效率很快的。

较大的字符串拼接时,五种方式的拼接效率由高到低排序是:

strings.Builder ≈ strings.Join > strings.Buffer > “+” > fmt

总结

1.io库属于底层接口定义库,其作用是是定义一些基本接口和一些基本常量,并对这些接口的作用给出说明,常见的接口有Reader、Writer等。一般用这个库只是为了调用它的一些常量,比如io.EOF。 2.ioutil库包含在io目录下,它的主要作用是作为一个工具包,里面有一些比较实用的函数,比如 ReadAll(从某个源读取数据)、ReadFile(读取文件内容)、WriteFile(将数据写入文件)、ReadDir(获取目录) 3.os库主要是跟操作系统打交道,所以文件操作基本都会跟os库挂钩,比如创建文件、打开一个文件等。这个库往往会和ioutil库、bufio库等配合使用 4.bufio库可以理解为在io库上再封装一层,加上了缓存功能。它可能会和ioutil库和bytes.Buffer搞混。 4.1 bufio VS ioutil库:两者都提供了对文件的读写功能,唯一的不同就是bufio多了一层缓存的功能,这个优势主要体现读取大文件的时候(ioutil.ReadFile是一次性将内容加载到内存,如果内容过大,很容易爆内存) 4.2 bufio VS bytes.Buffer:两者都提供一层缓存功能,它们的不同主要在于 bufio 针对的是文件到内存的缓存,而 bytes.Buffer 的针对的是内存到内存的缓存(个人感觉有点像channel,你也可以发现 bytes.Buffer 并没有提供接口将数据写到文件)。 5.bytes和strings库:这两个库有点迷,首先它们都实现了Reader接口,所以它们的不同主要在于针对的对象不同,bytes针对的是字节,strings针对的是字符串(它们的方法实现原理很相似)。另一个区别就是 bytes还带有Buffer的功能,但是 strings没提供。