一文学会Golang里拼接字符串的6种方式(性能对比)

news2024/11/25 18:15:50

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 string
	for 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 string
	for 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_strings

import "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 := 0
	l := 0
	count := 0
	for i, x := range a {
		n := len(x)
		if n == 0 {
			continue
		}
		if 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)
	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 value
	buf  []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 = opInvalid
	m, 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 = 0
	b.buf = b.buf[:m+n]
	return m
}

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

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

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

相关文章

LeetCode 3244.新增道路查询后的最短距离 II:贪心(跃迁合并)-9行py(O(n))

【LetMeFly】3244.新增道路查询后的最短距离 II&#xff1a;贪心&#xff08;跃迁合并&#xff09;-9行py&#xff08;O(n)&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-ii/ 给你一个整数 n 和一个二维…

MyBatis中特殊SQL的执行

目录 1.模糊查询 2.批量删除 3.动态设置表名 4.添加功能获取自增的主键 1.模糊查询 List<User> getUserByLike(Param("username") String username); <select id"getUserByLike" resultType"com.atguigu.mybatis.pojo.User">&…

ES 基本使用与二次封装

概述 基本了解 Elasticsearch 是一个开源的分布式搜索和分析引擎&#xff0c;基于 Apache Lucene 构建。它提供了对海量数据的快速全文搜索、结构化搜索和分析功能&#xff0c;是目前流行的大数据处理工具之一。主要特点即高效搜索、分布式存储、拓展性强 核心功能 全文搜索:…

Azkaban部署

首先我们需要现在相关的组件&#xff0c;在这里已经给大家准备好了相关的安装包&#xff0c;有需要的可以自行下载。 只需要启动hadoop集群就可以&#xff0c;如果现在你的hive是打开的&#xff0c;那么请你关闭&#xff01;&#xff01;&#xff01; 如果不关会造成证书冲突…

Jmeter中的定时器

4&#xff09;定时器 1--固定定时器 功能特点 固定延迟&#xff1a;在每个请求之间添加固定的延迟时间。精确控制&#xff1a;可以精确控制请求的发送频率。简单易用&#xff1a;配置简单&#xff0c;易于理解和使用。 配置步骤 添加固定定时器 右键点击需要添加定时器的请求…

JavaEE初学07

JavaEE初学07 MybatisORMMybatis一对一结果映射一对多结果映射 Mybatis动态sqlif标签trim标签where标签set标签foreach标签补充 Mybatis Mybatis是一款优秀的持久层框架&#xff0c;他支持自定义SQL、存储过程以及高级映射。Mybatis几乎免除了所有的JDBC代码以及设置参数和获取…

【layui】table的switch、edit修改

<title>简单表格数据</title><div class"layui-card layadmin-header"><div class"layui-breadcrumb" lay-filter"breadcrumb"><a>系统设置</a><a>简单表格数据</a></div> </div>&…

工具使用_docker容器_crossbuild

1. 工具简介 2. 工具使用 拉取 multiarch/crossbuild 镜像&#xff1a; docker pull multiarch/crossbuild 创建工作目录和示例代码&#xff1a; mkdir -p ~/crossbuild-test cd ~/crossbuild-test 创建 helloworld.c &#xff1a; #include <stdio.h>int main() …

Android 天气APP(三十七)新版AS编译、更新镜像源、仓库源、修复部分BUG

上一篇&#xff1a;Android 天气APP&#xff08;三十六&#xff09;运行到本地AS、更新项目版本依赖、去掉ButterKnife 新版AS编译、更新镜像源、仓库源、修复部分BUG 前言正文一、更新镜像源① 腾讯源③ 阿里源 二、更新仓库源三、修复城市重名BUG四、地图加载问题五、源码 前…

基于Java Springboot海洋馆预约系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

采用python3.12 +django5.1 结合 RabbitMQ 和发送邮件功能,实现一个简单的告警系统 前后端分离 vue-element

一、开发环境搭建和配置 #mac环境 brew install python3.12 python3.12 --version python3.12 -m pip install --upgrade pip python3.12 -m pip install Django5.1 python3.12 -m django --version #用于检索系统信息和进程管理 python3.12 -m pip install psutil #集成 pika…

STM32的中断(什么是外部中断和其他中断以及中断号是什么)

一、什么是EXTI 和NVIC EXTI&#xff08;External Interrupt/Event Controller&#xff09;EXTI 是外部中断/事件控制器&#xff0c;它负责处理外部信号变化&#xff0c;并将信号传递给中断控制器&#xff08;如 NVIC&#xff09;。主要负责以下功能&#xff1a; 外部事件检测…

【MyBatis】全局配置文件—mybatis.xml 创建xml模板

文章目录 模板文件配置元素typeAliasessettings 模板文件 创建模板 按照顺序打开【File】–>【settings】–>【Editor】–>【File and Code Templates】&#xff08;或直接搜索&#xff09; <?xml version"1.0" encoding"UTF-8" ?> <…

『VUE』34. 异步组件(详细图文注释)

目录 加载速度的优化示例代码总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 加载速度的优化 实际项目中你可能会有几十个组件,如果一开始就加载了全部组件(哪怕其中有些组件你暂时用不到)这无疑大大增加了响应时间,用户体验…

鸿蒙开发-音视频

Media Kit 特点 一般场合的音视频处理&#xff0c;可以直接使用系统集成的Video组件&#xff0c;不过外观和功能自定义程度低Media kit&#xff1a;轻量媒体引擎&#xff0c;系统资源占用低支持音视频播放/录制&#xff0c;pipeline灵活拼装&#xff0c;插件化扩展source/demu…

Spark——安装步骤详细教程

1、安装步骤 1、上传 cd /opt/modules 2、解压 tar -zxf spark-3.1.2-bin-hadoop3.2.tgz -C /opt/installs 3、重命名 cd /opt/installs mv spark-3.1.2-bin-hadoop3.2 spark-local 4、创建软链接 ln -s spark-local spark 5、配置环境变量&#xff1a; vi /etc/prof…

MFC工控项目实例三十一模拟量转化为工程量

实测工程量值&#xff08;变送器量程最大值-变送器量程最小值&#xff09;/&#xff08;数字量最大值-数字量最小值&#xff09;*&#xff08;当前采集工程量值-零点误差值&#xff09;。 相关程序代码 SEAL_PRESSURE.h class CSEAL_PRESSUREApp : public CWinApp { public:C…

svn 崩溃、 cleanup失败 怎么办

在使用svn的过程中&#xff0c;可能出现整个svn崩溃&#xff0c; 例如cleanup 失败的情况&#xff0c;类似于 这时可以下载本贴资源文件并解压。 或者直接访问网站 SQLite Download Page 进行下载 解压后得到 sqlite3.exe 放到发生问题的svn根目录的.svn路径下 右键呼出pow…

Win10系统开启了文件夹管控(文件夹限制访问)导致软件向系统公共文档目录写入失败的问题排查分享

目录 1、问题说明 2、查看系统是否开启了文件夹管控 3、在未安装杀毒软件的Win10电脑上可能会自动打开文件夹管控 4、到微软官网上查看Windows 安全中心的病毒和威胁防护与文件夹管控的详细说明 5、解决办法探讨 6、最后 C++软件异常排查从入门到精通系列教程(专栏文章列…

移远通信推出全新5G RedCap模组RG255AA系列,以更高性价比加速5G轻量化大规模商用

11月20&#xff0c;全球领先的物联网整体解决方案供应商移远通信宣布&#xff0c;正式推出其全新5G RedCap模组RG255AA系列。该系列模组支持5G NR独立组网&#xff08;SA&#xff09;和LTE Cat 4双模通信&#xff0c;具有高性能高集成度、低功耗、小尺寸、高性价比等优势&#…