Golang拼接字符串性能对比

g o l a n g golang golang s t r i n g string string类型是不可修改的,对于拼接字符串来说,本质上还是创建一个新的对象将数据放进去。主要有以下几种拼接方式

拼接方式介绍

1.使用 s t r i n g string string自带的运算符 + + +

ans = ans + s

2. 使用格式化输出 f m t . S p r i n t f fmt.Sprintf fmt.Sprintf

ans = fmt.Sprintf("%s%s", ans, s)

3. 使用 s t r i n g s strings strings j o i n join join函数

一般适用于将字符串数组转化为特定间隔符的字符串的情况

ans=strings.join(strs,",")

4. 使用 s t r i n g s . B u i l d e r strings.Builder strings.Builder

builder := strings.Builder{}
builder.WriteString(s)
return builder.String()

5. 使用 b y t e s . B u f f e r bytes.Buffer bytes.Buffer

buffer := new(bytes.Buffer)
buffer.WriteString(s)
return buffer.String()

6. 使用 [ ] b y t e []byte []byte,并且提前设置容量

ans := make([]byte, 0, len(s)*n)
ans = append(ans, s...)

性能对比

先写一个随机生成长度为 n n n的字符串的函数

func getRandomString(n int) string {var tmp = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"ans := make([]uint8, 0, n)for i := 0; i < n; i++ {ans = append(ans, tmp[rand.Intn(len(tmp))])}return string(ans)
}

接下来分别写出上述拼接方式的实现,假设每次都拼接n次字符串s后返回。

1.使用 s t r i n g string string自带的运算符 + + +

循环 n n n次,每次都令答案字符串 a n s + ans+ ans+源字符串 s s s

func plusOperatorJoin(n int, s string) string {var ans stringfor i := 0; i < n; i++ {ans = ans + s}return ans
}

2. 使用格式化输出 f m t . S p r i n t f fmt.Sprintf fmt.Sprintf

循环 n n n次,使用 f m t . S p r i n t f fmt.Sprintf fmt.Sprintf达到拼接的目的

func sprintfJoin(n int, s string) string {var ans stringfor i := 0; i < n; i++ {ans = fmt.Sprintf("%s%s", ans, s)}return ans
}

3. 使用 s t r i n g s strings strings j o i n join join函数

拼接同一个字符串的话不适合用 j o i n join join函数,所以跳过这种方式

4. 使用 s t r i n g s . B u i l d e r strings.Builder strings.Builder

初始化 s t r i n g s . B u i l d e r strings.Builder strings.Builder,循环 n n n次,每次调用 W r i t e S t r i n g WriteString WriteString方法

func stringBuilderJoin(n int, s string) string {builder := strings.Builder{}for i := 0; i < n; i++ {builder.WriteString(s)}return builder.String()
}

5. 使用 b y t e s . B u f f e r bytes.Buffer bytes.Buffer

初始化 b y t e s . B u f f e r bytes.Buffer bytes.Buffer,循环 n n n次,每次调用 W r i t e S t r i n g WriteString WriteString方法

func bytesBufferJoin(n int, s string) string {buffer := new(bytes.Buffer)for i := 0; i < n; i++ {buffer.WriteString(s)}return buffer.String()
}

6. 使用 [ ] b y t e []byte []byte,并且提前设置容量

定义 a n s ans ans b y t e byte byte数组,并提前设置容量为 l e n ( s ) ∗ n len(s)*n len(s)n

func bytesJoin(n int, s string) string {ans := make([]byte, 0, len(s)*n)for i := 0; i < n; i++ {ans = append(ans, s...)}return string(ans)
}

测试代码

先随机生成一个长度为10的字符串,然后拼接10000次。

package high_stringsimport "testing"func benchmark(b *testing.B, f func(int, string) string) {var str = getRandomString(10)for i := 0; i < b.N; i++ {f(10000, str)}
}func BenchmarkPlusOperatorJoin(b *testing.B) {benchmark(b, plusOperatorJoin)
}
func BenchmarkSprintfJoin(b *testing.B) {benchmark(b, sprintfJoin)
}
func BenchmarkStringBuilderJoin(b *testing.B) {benchmark(b, stringBuilderJoin)
}
func BenchmarkBytesBufferJoin(b *testing.B) {benchmark(b, bytesBufferJoin)
}
func BenchmarkBytesJoin(b *testing.B) {benchmark(b, bytesJoin)
}

在这里插入图片描述

测试结果:

使用 [ ] b y t e []byte []byte > s t r i n g s . B u i l d e r strings.Builder strings.Builder >= b y t e s . B u f f e r bytes.Buffer bytes.Buffer > f m t . S p r i n t f fmt.Sprintf fmt.Sprintf > + + +运算符

源码分析

1.使用 s t r i n g string string自带的运算符 + + +

代码在runtime\string.go


// 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 := 0l := 0count := 0for i, x := range a {n := len(x)if n == 0 {continue}if l+n < l {throw("string concatenation too long")}l += ncount++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)for _, x := range a {copy(b, x)b = b[len(x):]}return s
}
  • 首先计算拼接后的字符串长度
  • 如果只有一个字符串并且不在栈上就直接返回
  • 如果 b u f buf buf不为空并且 b u f buf buf可以放下这些字符串,就把拼接后的字符串放在 b u f buf buf里,否则在堆上重新申请一块内存
func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) {if buf != nil && l <= len(buf) {b = buf[:l]s = slicebytetostringtmp(&b[0], len(b))} else {s, b = rawstring(l)}return
}
// rawstring allocates storage for a new string. The returned
// string and byte slice both refer to the same storage.
// The storage is not zeroed. Callers should use
// b to set the string contents and then drop b.
func rawstring(size int) (s string, b []byte) {p := mallocgc(uintptr(size), nil, false)return unsafe.String((*byte)(p), size), unsafe.Slice((*byte)(p), size)
}
  • 然后遍历数组,将字符串 c o p y copy copy过去

2. 使用 s t r i n g s . B u i l d e r strings.Builder strings.Builder

介绍: s t r i n g s . B u i l d e r strings.Builder strings.Builder用于使用 W r i t e Write Write方法高效地生成字符串,它最大限度地减少了内存复制
拼接过程: b u i l d e r builder builder里有一个 b y t e byte byte类型的切片,每次调用 W r i t e S t r i n g WriteString WriteString的时候,是直接往该切片里追加字符串。因为切片底层的扩容机制是以倍数申请的,所以对比1而言,2的内存消耗要更少。
**结果返回:**在返回字符串的 S t r i n g String String方法里,是将 b u f buf buf数组转化为字符串直接返回的。
扩容机制: 想要缓冲区容量增加 n n n个字节,扩容后容量变为 2 ∗ l e n + n 2*len+n 2len+n

// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {addr *Builder // of receiver, to detect copies by valuebuf  []byte
}// String returns the accumulated string.
func (b *Builder) String() string {return unsafe.String(unsafe.SliceData(b.buf), len(b.buf))
}// 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
}
// 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
}

3. 使用 b y t e s . B u f f e r bytes.Buffer bytes.Buffer

介绍 b y t e s . B u f f e r bytes.Buffer bytes.Buffer s t r i n g s . B u i l d e r strings.Builder strings.Builder的底层都是 b y t e byte byte数组,区别在于扩容机制和返回字符串的 S t r i n g String String方法。
结果返回: 因为 b y t e s . B u f f e r bytes.Buffer bytes.Buffer实际上是一个流式的字节缓冲区,可以向尾部写入数据,也可以读取头部的数据。所以在返回字符串的 S t r i n g String String方法里,只返回了缓冲区里未读的部分,所以需要重新申请内存来存放返回的结果。内存会比 s t r i n g s . B u i l d e r strings.Builder strings.Builder稍慢一些。
扩容机制: 想要缓冲区容量至少增加 n n n个字节, m m m是未读的长度, c c c是当前的容量。
优化点在于如果 n < = c / 2 − m n <= c/2-m n<=c/2m,也就是当前容量的一半都大于等于现有的内容(未读的字节数)加上所需要增加的字节数,就复用当前的数组,把未读的内容拷贝到头部去。

We can slide things down instead of allocating a new slice. We only need m+n <= c to slide, but we instead let capacity get twice as large so we don’t spend all our time copying.
我们可以向下滑动,而不是分配一个新的切片。我们只需要m+n<=c来滑动,但我们让容量增加了一倍,这样我们就不会把所有的时间都花在复制上。

否则的话也是 2 ∗ l e n + n 2*len+n 2len+n的扩张

// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {buf      []byte // contents are the bytes buf[off : len(buf)]off      int    // read at &buf[off], write at &buf[len(buf)]lastRead readOp // last read operation, so that Unread* can work correctly.
}
// 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:])
}
// WriteString appends the contents of s to the buffer, growing the buffer as
// needed. The return value n is the length of s; err is always nil. If the
// buffer becomes too large, WriteString will panic with ErrTooLarge.
func (b *Buffer) WriteString(s string) (n int, err error) {b.lastRead = opInvalidm, ok := b.tryGrowByReslice(len(s))if !ok {m = b.grow(len(s))}return copy(b.buf[m:], s), nil
}// grow grows the buffer to guarantee space for n more bytes.
// It returns the index where bytes should be written.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) grow(n int) int {m := b.Len()// If buffer is empty, reset to recover space.if m == 0 && b.off != 0 {b.Reset()}// Try to grow by means of a reslice.if i, ok := b.tryGrowByReslice(n); ok {return i}if b.buf == nil && n <= smallBufferSize {b.buf = make([]byte, n, smallBufferSize)return 0}c := cap(b.buf)if n <= c/2-m {// We can slide things down instead of allocating a new// slice. We only need m+n <= c to slide, but// we instead let capacity get twice as large so we// don't spend all our time copying.copy(b.buf, b.buf[b.off:])} else if c > maxInt-c-n {panic(ErrTooLarge)} else {// Add b.off to account for b.buf[:b.off] being sliced off the front.b.buf = growSlice(b.buf[b.off:], b.off+n)}// Restore b.off and len(b.buf).b.off = 0b.buf = b.buf[:m+n]return m
}

字符串拼接性能及原理
GoLang bytes.Buffer基础使用方法详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/603563.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何将手机中termux用电脑的vnc显示

在电脑中我们同样需要下载 vnc 这里填写手机上的 IP&#xff1a;端口号 我的是 10.11.166.219:5902 下面填名字然后 手机端 输入sshd开始ssh这边就可以连接啦

java spring mvc 初探 web搭建过程详解

提前准备安装tomcat 设备&#xff1a;mac 第一步&#xff1a;下载 进入官网下载压缩包 注意&#xff1a;如果jdk版本是1.8&#xff0c;则tomcat需要v8才行&#xff0c;否则会报错 https://tomcat.apache.org/ 第二步&#xff1a;解压 解压后路径 /Users/you/Library/tomcat…

使用PyTorch实现去噪扩散模型

在深入研究去噪扩散概率模型(DDPM)如何工作的细节之前&#xff0c;让我们先看看生成式人工智能的一些发展&#xff0c;也就是DDPM的一些基础研究。 VAE VAE 采用了编码器、概率潜在空间和解码器。在训练过程中&#xff0c;编码器预测每个图像的均值和方差。然后从高斯分布中对…

CAN协议

文章目录 CAN介绍CAN的优势多主控制通信速度较快&#xff0c;通信距离远具有错误检测、错误通知和错误恢复功能故障封闭功能连接节点多 ISO11519-2物理层特性ISO11898物理层特性CAN 收发芯片 JTA1050 CAN 协议5 种帧5种帧介绍数据帧的构成帧起始仲裁段控制段数据段CRC段ACK段帧…

一文讲透使用Python绘制双纵轴线图

双纵轴线图主要用来展示两个因变量和一个自变量的关系&#xff0c;并且两个因变量的数值单位不同。具体来说&#xff0c;双纵轴线图是指在一幅图上有一个横轴和两个纵轴&#xff0c;适用于三个变量。两个纵轴分别表示一个变量&#xff0c;横轴变量同时适用于两个纵轴上的变量&a…

报错curl: (6) Could not resolve host: raw.githubusercontent...的解决办法

我起初想要在macOS系统安装pip包&#xff0c;首先在终端安装homebrew&#xff0c;敲了命令&#xff1a;/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent...)" 之后触发的报错&#xff0c;报错内容&#xff1a;curl: (6) Could not resolve host: raw.…

【大数据】Flink CDC 的概览和使用

Flink CDC 的概览和使用 1.什么是 CDC2.什么是 Flink CDC3.Flink CDC 前生今世3.1 Flink CDC 1.x3.2 Flink CDC 2.x3.3 Flink CDC 3.x 4.Flink CDC 使用5.Debezium 标准 CDC Event 格式详解 1.什么是 CDC CDC&#xff08;Change Data Capture&#xff0c;数据变更抓取&#xf…

专业级的渗透测试服务,助力航空业数字化安全启航

​某知名航空公司是中国首批民营航空公司之一&#xff0c;运营国内外航线200多条&#xff0c;也是国内民航最高客座率的航空公司之一。在数字化发展中&#xff0c;该航空公司以数据驱动决策&#xff0c;通过精细化管理、数字创新和模式优化等方式&#xff0c;实现了精准营销和个…

k8s之flink的几种创建方式

在此之前需要部署一下私人docker仓库&#xff0c;教程搭建 Docker 镜像仓库 注意&#xff1a;每台节点的daemon.json都需要配置"insecure-registries": ["http://主机IP:8080"] 并重启 一、session 模式 Session 模式是指在 Kubernetes 上启动一个共享的…

智慧旅游景区解决方案:PPT全文49页,附下载

关键词&#xff1a;智慧景区建设&#xff0c;智慧旅游平台&#xff0c;智慧旅游运营检测系统项目&#xff0c;智慧文旅&#xff0c;智慧景区开发与管理&#xff0c;智慧景区建设核心&#xff0c;智慧景区开发与管理 一、智慧景区建设现状 1、基础设施建设&#xff1a;智慧景区…

推荐收藏!万字长文带入快速使用 keras

这些年&#xff0c;有很多感悟&#xff1a;一个人精力是有限的&#xff0c;一个人视野也有有限的&#xff0c;你总会不经意间发现优秀人的就在身边。 看我文章的小伙伴应该经常听我说过的一句话&#xff1a;技术要学会交流、分享&#xff0c;不建议闭门造车。一个人可以走的很…

Keil5,ARM编译器 软件优化注意事项

优化C代码中的环路终止 循环是大多数程序中的常见结构。由于大量的执行时间通常花费在循环中&#xff0c;因此值得关注时间关键循环。 如果不谨慎地编写&#xff0c;环路终止条件可能会导致大量开销。在可能的情况下&#xff1a; 使用简单的终止条件。 写入倒计时到零循环。…

MySQL三种常见存储引擎【理论】【需动手操作】

先放一个大佬的博客 等以后有时间按大佬写的 动手操作一下 链接 MySOL 的存储引擎是指 MySOL 数据库管理系统中用于处理数据存诸和检索的组件。 MySOL 常用的存储引擎有以下几个: InnoDB: InnoDB 是 MySQL(5.5)的默认存储引擎&#xff0c;支持事务处理、行级锁定和物理外键约…

2024年超详细的Python3学习路径规划

前言 基于Python3.5 1.第一阶段基础&#xff08;必须&#xff09; Python3 环境搭建Python3 基础语法Python3 基本数据类型Python3 数据类型转换Python3 解释器Python3 注释Python3 运算符Python3 数字(Number)Python3 字符串Python3 列表Python3 元组Python3 字典Python3 集…

dnSpy调试工具二次开发1-新增菜单

测试环境&#xff1a; window 10 visual studio 2019 版本号&#xff1a;16.11.15 .net framework 4.8 开发者工具包 下载 .NET Framework 4.8 | 免费官方下载 .net 5开发者工具包 下载 .NET 5.0 (Linux、macOS 和 Windows) 利用git拉取代码(源码地址&#xff1a;Gi…

启动IDEA报错,web servcer failed to start.port 8080 was already in use.

启动IDEA报错&#xff0c;web servcer failed to start.port 8080 was already in use. 问题现状 启动IDEA失败&#xff0c;端口被占用。 解决办法&#xff1a; 使用netstat -ano指令&#xff0c;查看端口占用情况 因为我是win11的系统&#xff0c;使用指令时出现如下提示。…

【IC设计】移位寄存器

目录 理论讲解背景介绍什么是移位寄存器按工作模式分类verilog语法注意事项 设计实例循环移位寄存器算术双向移位寄存器5位线性反馈移位寄存器伪随机码发生器3位线性反馈移位寄存器32位线性反馈移位寄存器串行移位寄存器&#xff08;打4拍&#xff09;双向移位寄存器&#xff1…

c语言题目之统计二级制数中1的个数

文章目录 题目一、方法1二、方法2三&#xff0c;方法3总结 题目 统计二进制数中1的个数 输入一行&#xff0c;输出一行 输入&#xff1a; 输入一个整数 输出&#xff1a; 输出存储在内存中二进制的1的个数 一、方法1 之前的文章中&#xff0c;小编写了有关于内存在二进制中的存…

Fiddler工具 — 8.会话列表(Session List)

1、会话列表说明 Fiddler抓取到的每条HTTP请求&#xff08;每一条称为一个session&#xff09;。 主要包含了请求的ID编号、状态码、协议、主机名、URL、内容类型、body大小、进程信息、自定义备注等信息。如下图&#xff1a; 说明&#xff1a; 名称含义#抓取HTTP Request的顺…

Ribbon相关问题及答案(2024)

1、Ribbon是什么&#xff0c;它在微服务架构中扮演什么角色&#xff1f; Ribbon是一个客户端负载均衡器&#xff0c;它在微服务架构中扮演着关键性的角色。Ribbon的设计理念是在客户端进行服务发现和负载均衡&#xff0c;这种方式不同于传统的通过中心化的负载均衡器&#xff…