二. go 高性能编程之 字符串拼接问题

一. 字符串高效拼接

  1. 在Go中字符串是不可变的,拼接字符串时底层实际是创建了一个新的字符串,代码中有大量的字符串拼接,对性能有会有影响
  2. 常见的拼接方式
	//1.使用"+"
	for i := 0; i < n; i++ {
		s += str
	}

	//2.使用fmt.Sprintf
	for i := 0; i < n; i++ {
		s = fmt.Sprintf("%s%s", s, str)
	}
	
	//3.使用strings.Builder
	var builder strings.Builder
	for i := 0; i < n; i++ {
		builder.WriteString(str)
	}
	
	//4.使用 bytes.Buffer
	func bufferConcat(n int, s string) string {
		buf := new(bytes.Buffer)
		for i := 0; i < n; i++ {
			buf.WriteString(s)
		}
		return buf.String()
	}

	//5.使用 []byte
	func byteConcat(n int, str string) string {
		buf := make([]byte, 0)
		for i := 0; i < n; i++ {
			buf = append(buf, str...)
		}
		return string(buf)
	}

	//6.如果长度是可预知的,那么创建 []byte 时,我们还可以预分配切片的容量(cap)
	func preByteConcat(n int, str string) string {
		buf := make([]byte, 0, n*len(str))
		for i := 0; i < n; i++ {
			buf = append(buf, str...)
		}
		return string(buf)
	}
  1. 性能分析后得到结果
  1. 使用 + 和 fmt.Sprintf 的效率是最低的,和其余的方式相比,性能相差约 1000 倍,而且消耗了超过 1000 倍的内存。当然 fmt.Sprintf 通常是用来格式化字符串的,一般不会用来拼接字符串。
  2. strings.Builder、bytes.Buffer 和 []byte 的性能差距不大,而且消耗的内存也十分接近,
  3. 能够预知长度的前提下使用预分配preByteConcat性能最好且消耗内存最小,在字符串拼接的过程中,不需要进行字符串的拷贝,也不需要分配新的内存,所以性能最好,且内存消耗最小
  1. 推荐使用 strings.Builder 来拼接字符串,并且string.Builder 也提供了预分配内存的方式 Grow
func builderConcat(n int, str string) string {
	var builder strings.Builder
	builder.Grow(n * len(str))
	for i := 0; i < n; i++ {
		builder.WriteString(str)
	}
	return builder.String()
}
  1. 与预分配内存的 []byte 相比,因为省去了 []byte 和字符串(string) 之间的转换,内存分配次数还减少了 1 次,内存消耗减半

strings.Builder 和 + 的原理分析

  1. strings.Builder 和 + 性能和内存消耗差距如此巨大,是因为两者的内存分配方式不一样:
  1. 当使用 + 拼接 2 个字符串时,需要开辟一段新的空间,新空间的大小是原来两个字符串的大小之和。以此类推。假设一个字符串大小为 10 byte,拼接 1w 次,就需要多次计算多次开辟新空间
  2. strings.Builder,bytes.Buffer,包括切片 []byte 的内存是以倍数申请的。例如,初始大小为 0,当第一次写入大小为 10 byte 的字符串时,则会申请大小为 16 byte 的内存(恰好大于 10 byte 的 2 的指数),第二次写入 10 byte 时,内存不够,则申请 32 byte 的内存,第三次写入内存足够,则不申请新的,以此类推。在实际过程中,超过一定大小,比如 2048 byte 后,申请策略上会有些许调整。我们可以通过打印 builder.Cap() 查看字符串拼接过程中,strings.Builder 的内存申请过程

strings.Builder 和 bytes.Buffer 的原理分析

  1. strings.Builder 和 bytes.Buffer 底层都是 []byte 数组,但是bytes.Buffer 转化为字符串时会重新申请内存,存放生成的字符串,而 strings.Builder 直接将底层的 []byte 转换成了字符串类型返回,所以 strings.Builder 比 bytes.Buffer 略快约 10%
  2. bytes.Buffer
func (b *Buffer) String() string {
	if b == nil {
		// Special case, useful in debugging.
		return "<nil>"
	}
	return string(b.buf[b.off:])
}
  1. strings.Builder
// String returns the accumulated string.
func (b *Builder) String() string {
	return *(*string)(unsafe.Pointer(&b.buf))
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值