go字符串拼接操作

1. 使用 + 拼接

通过查看汇编代码可知 + 实际上调用的是 runtime/string.go中的concatstrings 函数,该函数源代码如下:

// concatstrings implements a Go string concatenation x+y+z+...
// The operands are passed in the slice a.
// If buf != nil, the compiler has determined that the result does not
// escape the calling function, so the string data can be stored in buf
// if small enough.
func concatstrings(buf *tmpBuf, a []string) string {
idx := 0
l := 0
count := 0
for i, x := range a {
    n := len(x)
    if n == 0 {
        continue
    }
    if l+n < l { //如果需要拼接的字符串太多,其字节数超过int的最大值,l将变为负数,所以l+n会小于l
        throw("string concatenation too long")
    }
    l += n
    count++
    idx = i
}
if count == 0 {
    return ""
}

// If there is just one string and either it is not on the stack
// or our result does not escape the calling frame (buf != nil),
// then we can return that string directly.
if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
    return a[idx]
}
s, b := rawstringtmp(buf, l) //上面计算出所需要的字节空间,通过copy函数完成拼接
for _, x := range a {
    copy(b, x)
    b = b[len(x):]
}
return s
}

通过源码分析,使用 x+y+z+... 的方式完成一次拼接将与下面提到的strings.Join等方式没有太多的性能差异,但是在循环中出现性能损耗主要是在内存分配方面,因为每一次拼接都需要一次内存分配。即在一次拼接时+ 性能很好,但多次拼接性能就会变差。注意在最后没有将[]byte转换成string的损耗,而strings.Join在最后有将[]byte转换为string的损耗,故一次拼接其性能要比strings.Join好。

2. strings.Join

// Join concatenates the elements of a to create a single string. The separator string
  // sep is placed between elements in the resulting string.
  func Join(a []string, sep string) string {
    switch len(a) {
    case 0:
        return ""
    case 1:
        return a[0]
    case 2:
        // Special case for common small values.
        // Remove if golang.org/issue/6714 is fixed
        return a[0] + sep + a[1]
    case 3:
        // Special case for common small values.
        // Remove if golang.org/issue/6714 is fixed
        return a[0] + sep + a[1] + sep + a[2]
    }
    n := len(sep) * (len(a) - 1)
    for i := 0; i < len(a); i++ {
        n += len(a[i])
    }

    b := make([]byte, n)
    bp := copy(b, a[0])
    for _, s := range a[1:] {
        bp += copy(b[bp:], sep)
        bp += copy(b[bp:], s)
    }
    return string(b) //注意这里有转换的损耗
  }

该方法优于+ 的地方在于一次性分配所需空间,而+ 每一次迭代都需要重新分配。该方法一次调用其性能足够好,但多次调用就需要多次分配内存,其性能差于下述的bytes.Buffer。

3. bytes.Buffer

源码实现在bytes/buffer.go中 小内存优化,能提前预分配内存,内存不足时*2倍增长,但是最后获取string结果有[]byte转string的消耗,故bytes.Buffer在一次初始化(提前计算总长度,一次性预分配好内存更好),多次字符串连接操作,最后一次性获取string结果的场景中是最快的。灵活性是最强的
byte.Buffer之所以在多次连接操作中性能会更好,是因为当内存不够时会以2倍重新分配,在后面将减少内存分配的次数,从而提升性能。

4. fmt.Sprintf

综上,
如果是少量小文本拼接,用 “+” 就好
如果是大量小文本拼接,用 strings.Join
如果是大量大文本拼接,用 bytes.Buffer

再次综上
+操作符 通过汇编可知实现在runtime/string.go中, 主要是concatstrings函数 短字符串优化,没有借助[]byte造成转换string的消耗,故单次调用+操作符是最快的。灵活性最差。
bytes.Buffer 源码实现在bytes/buffer.go中 小内存优化,能提前预分配内存,内存不足时*2倍增长,但是最后获取string结果有[]byte转string的消耗,故bytes.Buffer在一次初始化(提前计算总长度,一次性预分配好内存更好),多次字符串连接操作,最后一次性获取string结果的场景中是最快的。灵活性是最强的
strings.Join 源码实现在strings/strings.go中 少量字符串连接优化,一次性分配内存,有[]byte转换string的消耗,故单次调用能达到bytes.Buffer的最好效果,但是它不够灵活
fmt.Sprintf 源码实现在fmt/print.go中 因为a…interface{}有参数转换的消耗, 借助[]byte每次添加调用append,逻辑相对复杂,最后获取结果有[]byte转string的消耗,故fmt.Sprintf一般要慢于bytes.Buffer和strings.Join,灵活性和strings.Join差不多
结论
单次调用性能:操作符+>strings.Join>=bytes.Buffer>fmt.Sprintf 灵活性:bytes.Buffer>fmt.Sprintf>=strings.Join>操作符+
正确使用,多次连接字符串操作的情况下,bytes.Buffer应该是最快的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值