首页 > 后端开发 > Golang > 正文

Golang的字符串拼接哪种方式最高效 对比+、fmt.Sprintf等性能差异

P粉602998670
发布: 2025-08-07 14:32:01
原创
373人浏览过

golang中,频繁拼接字符串时最高效的方式是使用strings.builder。1. +操作符因频繁内存分配和复制导致性能差;2. fmt.sprintf因反射和格式化逻辑开销大;3. strings.builder通过预分配内存减少扩容次数显著提升性能;4. bytes.buffer与strings.builder性能接近,适合处理字节数据。若已知最终长度,可配合grow方法进一步优化性能。循环拼接或动态拼接场景推荐使用strings.builder,而少量固定拼接可用+操作符以保持代码简洁。实际开发中应避免在循环中滥用+、过度使用fmt.sprintf、忽略grow方法及未经测试盲目优化。

Golang的字符串拼接哪种方式最高效 对比+、fmt.Sprintf等性能差异

在Golang中,对于大多数需要频繁拼接字符串的场景,

strings.Builder
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
无疑是效率最高的选择。它通过预分配内存并减少底层数组的重新分配次数,显著优于使用
+
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作符或
fmt.Sprintf
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
进行拼接。当然,具体选择还得看你的使用场景和对性能的极致追求程度。

Golang的字符串拼接哪种方式最高效 对比+、fmt.Sprintf等性能差异

解决方案

谈到Golang的字符串拼接,这可真是个老生常谈的话题,但每次深入聊起来,总能发现一些被忽视的细节。说实话,我个人在项目里,如果不是为了特别简单的日志输出或者极少数的短字符串拼接,我都会下意识地倾向于

strings.Builder
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。至于为什么,我们来掰扯掰扯。

1.

+
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作符:简单粗暴,但有代价

立即学习go语言免费学习笔记(深入)”;

Golang的字符串拼接哪种方式最高效 对比+、fmt.Sprintf等性能差异

这是最直观的拼接方式,代码写起来也最简洁。比如

s := "hello" + " " + "world"
登录后复制
。但它背后的机制是:每次
+
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作都会创建一个新的字符串。因为Go中的字符串是不可变的,每次拼接都意味着分配一块新的内存,然后将原有的内容和新内容复制过去。如果在一个循环里频繁使用
+
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,那内存分配和复制的开销会非常大,性能自然就下去了。

package main

import (
    "fmt"
    "strings"
    "testing"
)

// + 操作符拼接
func BenchmarkPlusConcatenation(b *testing.B) {
    var s string
    for i := 0; i < b.N; i++ {
        s += "a" // 模拟多次拼接
    }
    _ = s
}
登录后复制

2.

fmt.Sprintf
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
:格式化利器,拼接慢郎中

Golang的字符串拼接哪种方式最高效 对比+、fmt.Sprintf等性能差异

fmt.Sprintf
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
功能强大,可以方便地将各种类型的数据格式化成字符串。比如
s := fmt.Sprintf("%s %s", "hello", "world")
登录后复制
。但它的强大也带来了性能上的负担。
fmt.Sprintf
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
内部涉及反射、接口转换以及复杂的格式化逻辑,这些操作都比简单的内存复制要慢得多。所以,如果你的需求仅仅是字符串拼接,而不是复杂的格式化,用它来拼接字符串简直是杀鸡用牛刀,效率非常低。

// fmt.Sprintf 拼接
func BenchmarkFmtSprintfConcatenation(b *testing.B) {
    var s string
    for i := 0; i < b.N; i++ {
        s = fmt.Sprintf("%s%s", s, "a") // 模拟多次拼接
    }
    _ = s
}
登录后复制

3.

strings.Builder
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
:性能优选,内存优化

这是Golang标准库提供的一种高效的字符串构建方式。它的核心思想是预先分配一块足够大的内存缓冲区,然后将要拼接的字符串逐一追加到这个缓冲区中。只有当缓冲区不足时,才会进行一次性的扩容操作。这大大减少了内存的分配和复制次数,尤其是在需要拼接大量字符串时,性能优势非常明显。我个人觉得,这东西,用起来真是香。

// strings.Builder 拼接
func BenchmarkStringBuilderConcatenation(b *testing.B) {
    var sb strings.Builder
    for i := 0; i < b.N; i++ {
        sb.WriteString("a") // 模拟多次拼接
    }
    _ = sb.String()
}

// strings.Builder 预分配内存拼接 (如果知道大致长度)
func BenchmarkStringBuilderWithGrowConcatenation(b *testing.B) {
    var sb strings.Builder
    // 假设我们知道最终字符串大概的长度
    sb.Grow(b.N) // 预分配内存
    for i := 0; i < b.N; i++ {
        sb.WriteString("a")
    }
    _ = sb.String()
}
登录后复制

4.

bytes.Buffer
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
:字节层面的灵活构建

bytes.Buffer
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
strings.Builder
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
在原理上非常相似,都是通过一个可增长的字节切片来构建数据。不同之处在于,
bytes.Buffer
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作的是
[]byte
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,最终需要通过
String()
登录后复制
方法转换为字符串。如果你在处理二进制数据,或者你的数据源本身就是字节切片,那么
bytes.Buffer
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
可能会更顺手。性能上,它和
strings.Builder
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
旗鼓相当。

import "bytes"

// bytes.Buffer 拼接
func BenchmarkBytesBufferConcatenation(b *testing.B) {
    var buf bytes.Buffer
    for i := 0; i < b.N; i++ {
        buf.WriteString("a") // 模拟多次拼接
    }
    _ = buf.String()
}
登录后复制

简单Benchmark结果概览 (通常情况,具体数值取决于环境和N值):

方法 性能 (相对) 备注
@@######@@ 最差 频繁内存分配和复制
@@######@@ 较差 涉及反射和格式化,开销大
@@######@@ 最佳 预分配内存,减少复制,推荐
@@######@@ 最佳 (可优化) 明确长度时进一步减少扩容,极致性能
@@######@@ 接近最佳 字节操作,与@@######@@类似

在实际项目中,当需要拼接的字符串数量不确定或较多时,

+
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
几乎是你的不二之选。如果只是两三个固定字符串的拼接,用
fmt.Sprintf
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
也无伤大雅,毕竟可读性在那儿。

Golang字符串拼接的性能瓶颈究竟在哪里?

要理解拼接的性能差异,我们得扒开它内部的皮肉看看。字符串拼接的性能瓶颈,核心问题其实都指向了内存分配与数据复制

Go语言中的字符串是不可变的(immutable)。这意味着一旦一个字符串被创建,它的内容就不能被修改。当你使用

strings.Builder
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作符拼接两个字符串时,Go运行时并不会在原地修改第一个字符串,而是:

  1. 计算新字符串的总长度
    strings.Builder.Grow()
    登录后复制
  2. 分配一块新的内存空间:这块空间足以容纳新计算出的总长度。
  3. 复制数据:将
    bytes.Buffer
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的内容复制到新内存空间的开头,然后将
    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的内容复制到
    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    内容之后。
  4. 返回新字符串:这个新字符串指向刚刚分配并填充好的内存空间。

这个过程,如果只发生一两次,那开销几乎可以忽略不计。但如果在循环中,比如拼接1000个字符,

+
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,那么每次循环都会发生上述的内存分配和数据复制。第一次分配1字节,第二次2字节,第三次3字节... 最终,你可能分配了
+
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
字节的内存,并且进行了大量的复制操作。这就像你每次往一个箱子里放东西,都得先找个新箱子,把旧箱子里的东西和新东西一起搬过去。效率可想而知。

len(s1) + len(s2)
登录后复制
的瓶颈则更复杂一些。它不仅有上述的内存分配和复制,还额外增加了:

  • 反射开销
    s1
    登录后复制
    登录后复制
    需要检查传入参数的类型,以便正确地格式化它们。这涉及到Go的反射机制,虽然强大,但性能上会有额外损耗。
  • 接口转换:所有的参数都会被包装成
    s2
    登录后复制
    类型,这在内部也需要一些额外的处理。
  • 格式化逻辑:根据不同的格式动词(如
    s1
    登录后复制
    登录后复制
    ,
    s += "a"
    登录后复制
    ,
    1+2+3+...+N
    登录后复制
    等),它需要执行不同的格式化逻辑,这比简单的字符串复制要复杂得多。

fmt.Sprintf
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
fmt.Sprintf
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
则巧妙地规避了这些问题。它们内部维护一个可增长的字节切片(
interface{}
登录后复制
)。当你调用
%s
登录后复制
时,它们会尝试将内容追加到现有的切片中。只有当现有容量不足以容纳新内容时,才会进行一次扩容操作。这个扩容策略通常是指数级的(比如每次扩容到当前容量的两倍),这样就大大减少了扩容的频率。内存分配和数据复制虽然依然存在,但频率和总开销都大幅降低了。这就像你有一个大箱子,每次装东西,只要箱子还有地方就直接放进去;只有箱子满了,才去换一个更大的箱子。

所以,归根结底,瓶颈在于频繁的内存分配、数据复制以及像

%d
登录后复制
这类工具的额外处理开销。

如何选择最适合的字符串拼接方式?

选择最合适的字符串拼接方式,绝不是一刀切的事情,得看你的具体场景和需求。没有银弹,只有最匹配的。

  1. 极少量、固定字符串拼接

    • 选择:
      %v
      登录后复制
      操作符
    • 理由: 这种情况下,
      strings.Builder
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      操作符的性能开销可以忽略不计,而且代码最简洁,可读性最好。比如
      bytes.Buffer
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      ,这种场景下,为了那么一点点性能差异去用
      []byte
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      ,反而会让代码显得啰嗦。
  2. 需要复杂格式化输出

    • 选择:
      WriteString
      登录后复制
    • 理由: 当你需要将数字、布尔值、结构体等非字符串类型数据与字符串混合,并按照特定格式输出时,
      fmt.Sprintf
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      是你的首选。它的强大格式化能力是其他方式无法比拟的。性能虽然不是最高,但为了功能性,这点牺牲是值得的。但要记住,如果仅仅是拼接字符串,就别用它了。
  3. 循环中拼接、动态拼接、拼接数量不确定或较多

    • 选择:
      +
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
    • 理由: 这是
      +
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      大显身手的场景。无论是从数据库查询结果组装长字符串,还是处理用户输入构建复杂查询,只要涉及多次追加,它都能提供最佳的性能和内存效率。它能有效避免
      log.Println("User " + name + " logged in.")
      登录后复制
      操作符带来的频繁内存分配和数据复制。
  4. 处理字节数据,或最终需要字节切片

    • 选择:
      strings.Builder
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
    • 理由: 如果你的数据源本身就是
      fmt.Sprintf
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      ,或者你的最终输出是
      fmt.Sprintf
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      而不是
      strings.Builder
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      ,那么
      strings.Builder
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      会更自然一些。它和
      +
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      在性能上非常接近,只是操作的类型不同。
  5. 追求极致性能,且已知最终长度

    • 选择:
      bytes.Buffer
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      配合
      []byte
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      方法
    • 理由: 如果你能预估最终字符串的大致长度,比如你知道要拼接1000个字符,那么在初始化
      []byte
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      时调用
      string
      登录后复制
      登录后复制
      ,可以预先分配好足够的内存,彻底避免后续的扩容操作,从而获得理论上的最佳性能。但话说回来,凡事没有绝对,过度优化也可能适得其反,只有在性能瓶颈确实出现在这里时,才值得这么做。

总结一下:

  • 小而美,图省事:
    bytes.Buffer
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
  • 功能全,要格式:
    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
  • 量大活多,要高效:
    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
  • 字节流,要灵活:
    Grow()
    登录后复制
    登录后复制
    登录后复制

记住,在选择任何优化方案之前,最好先进行性能分析(profiling)和基准测试(benchmarking)。很多时候,你认为的性能瓶颈可能根本不是瓶颈,而盲目的优化反而会增加代码的复杂性。

实际项目中,有哪些常见的字符串拼接误区?

在日常开发中,我见过不少开发者在字符串拼接上踩坑,有些是经验不足,有些则是对Go语言特性理解不够深入。避开这些误区,能让你的代码更健壮、性能更好。

  1. 在循环中盲目使用

    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    操作符 这是最常见也最致命的误区。我看到过不少新手或者从其他语言转过来的开发者,习惯性地在循环里用
    builder.Grow(1000)
    登录后复制
    这种写法。比如:

    +
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制

    这种代码在小数据量时可能不明显,一旦数据量上去,性能会急剧恶化,内存占用也会飙升,甚至可能导致程序崩溃。正确做法应该是使用

    fmt.Sprintf
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制

  2. 过度依赖

    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    进行简单拼接
    bytes.Buffer
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的功能非常强大,但它的代价就是性能。如果你仅仅是为了拼接两个字符串,比如
    +
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    ,这比
    result += item
    登录后复制
    或者
    // 错误示例:在循环中滥用 +
    func buildLongStringBad() string {
        s := ""
        for i := 0; i < 10000; i++ {
            s += "some_text" // 每次循环都会创建新字符串并复制
        }
        return s
    }
    登录后复制
    慢得多。
    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    应该保留给那些确实需要复杂格式化,比如将数字转换为字符串,或者按特定模板输出的场景。

  3. 忽略

    fmt.Sprintf
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    fmt.Sprintf
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    方法
    虽然
    s := fmt.Sprintf("%s%s", str1, str2)
    登录后复制
    本身已经很高效了,但如果你能预估最终字符串的大致长度,不使用
    str1 + str2
    登录后复制
    方法就等于放弃了一次优化的机会。
    builder.WriteString(str1).WriteString(str2)
    登录后复制
    可以预先分配足够的内存,避免后续的多次扩容。

    fmt.Sprintf
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制

    当然,如果无法准确预估,不使用

    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    也无妨,
    Grow
    登录后复制
    登录后复制
    的默认扩容策略已经足够优秀。

  4. 不进行性能测试和基准测试就做优化 这是所有性能优化中最常见的误区。开发者往往凭经验或直觉认为某个地方是瓶颈,然后投入大量精力去优化,结果发现效果甚微,甚至引入了新的bug。正确的姿势是:

    • 先用
      strings.Builder
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      登录后复制
      等工具定位真正的性能瓶颈。
    • 对优化前后的代码进行基准测试(
      Grow()
      登录后复制
      登录后复制
      登录后复制
      ),用数据说话。
      只有当数据表明字符串拼接确实是性能瓶颈时,才值得花时间去优化它。
  5. 混淆字符串和字节切片的操作 Go的

    Grow()
    登录后复制
    登录后复制
    登录后复制
    // 更好的做法:使用 Grow 预分配
    func buildLongStringGood() string {
        var b strings.Builder
        // 假设我们知道大概会拼接 10000 * len("some_text") 的长度
        b.Grow(10000 * len("some_text")) 
        for i := 0; i < 10000; i++ {
            b.WriteString("some_text")
        }
        return b.String()
    }
    登录后复制
    是不同的类型,虽然它们之间可以相互转换,但转换本身是有开销的。如果你在处理大量字节数据,并且最终结果也是字节数据,那么全程使用
    Grow
    登录后复制
    登录后复制
    strings.Builder
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    会更高效,避免不必要的
    pprof
    登录后复制
    go test -bench=.
    登录后复制
    转换。

这些误区,说到底,都是对Go语言的内存模型、字符串特性以及标准库工具理解不够深入的表现。多动手实践,多做Benchmark,你会对这些细节有更直观的感受。

string
登录后复制
登录后复制
[]byte
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
[]byte
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
bytes.Buffer
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
string([]byte)
登录后复制
[]byte(string)
登录后复制

以上就是Golang的字符串拼接哪种方式最高效 对比+、fmt.Sprintf等性能差异的详细内容,更多请关注php中文网其它相关文章!

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号