go基础10 -字符串的高效构造与转换
前面提到过,Go原生支持通过+/+=操作符来连接多个字符串以构造一个更长的字符串,并且通过+/+=操作符的字符串连接构造是最自然、开发体验最好的一种。
但Go还提供了其他一些构造字符串的方法,比如:
● 使用fmt.Sprintf;
● 使用strings.Join;
● 使用strings.Builder;
● 使用bytes.Buffer。
在这些方法中哪种方法最为高效呢?我们使用基准测试的数据作为参考:
var sl []string = []string{
"Rob Pike ",
"Robert Griesemer ",
"Ken Thompson ",
}
func concatStringByOperator(sl []string) string {
var s string
for _, v := range sl {
s += v
}
return s
}
func concatStringBySprintf(sl []string) string {
var s string
for _, v := range sl {
s = fmt.Sprintf("%s%s", s, v)
}
return s
}
func concatStringByJoin(sl []string) string {
return strings.Join(sl, "")
}
func concatStringByStringsBuilder(sl []string) string {
var b strings.Builder
for _, v := range sl {
b.WriteString(v)
}
return b.String()
}
func concatStringByStringsBuilderWithInitSize(sl []string) string {
var b strings.Builder
b.Grow(64)for _, v := range sl {
b.WriteString(v)
}
return b.String()
}
func concatStringByBytesBuffer(sl []string) string {
var b bytes.Buffer
for _, v := range sl {
b.WriteString(v)
}
return b.String()
}
func concatStringByBytesBufferWithInitSize(sl []string) string {
buf := make([]byte, 0, 64)
b := bytes.NewBuffer(buf)
for _, v := range sl {
b.WriteString(v)
}
return b.String()
}
func BenchmarkConcatStringByOperator(b *testing.B) {
for n := 0; n < b.N; n++ {
concatStringByOperator(sl)
}
}
func BenchmarkConcatStringBySprintf(b *testing.B) {
for n := 0; n < b.N; n++ {
concatStringBySprintf(sl)
}
}
func BenchmarkConcatStringByJoin(b *testing.B) {
for n := 0; n < b.N; n++ {
concatStringByJoin(sl)
}
}
func BenchmarkConcatStringByStringsBuilder(b *testing.B) {
for n := 0; n < b.N; n++ {
concatStringByStringsBuilder(sl)
}
}
func BenchmarkConcatStringByStringsBuilderWithInitSize(b *testing.B) {
for n := 0; n < b.N; n++ {
concatStringByStringsBuilderWithInitSize(sl)
}
}
func BenchmarkConcatStringByBytesBuffer(b *testing.B) {
for n := 0; n < b.N; n++ {
concatStringByBytesBuffer(sl)
}
}
func BenchmarkConcatStringByBytesBufferWithInitSize(b *testing.B) {
for n := 0; n < b.N; n++ {
concatStringByBytesBufferWithInitSize(sl)
}
}
运行该基准测试:
$go test -bench=. -benchmem ./string_concat_benchmark_test.go
goos: darwin
goarch: amd64
BenchmarkConcatStringByOperator-8 11744653 89.1 ns/op 80 B/op 2 allocs/op
BenchmarkConcatStringBySprintf-8 2792876 420 ns/op 176 B/op 8 allocs/op
BenchmarkConcatStringByJoin-8 22923051 49.1 ns/op 48 B/op 1 allocs/op
BenchmarkConcatStringByStringsBuilder-8 11347185 96.6 ns/op 112 B/op 3 allocs/op
BenchmarkConcatStringByStringsBuilderWithInitSize-8 26315769 42.3 ns/op 64 B/op 1 allocs/op
BenchmarkConcatStringByBytesBuffer-8 14265033 82.6 ns/op 112 B/op 2 allocs/op
BenchmarkConcatStringByBytesBufferWithInitSize-8 24777525 48.1 ns/op 48 B/op 1 allocs/op
PASS
ok command-line-arguments 8.816s
从基准测试的输出结果的第三列,即每操作耗时的数值来看:
● 做了预初始化的strings.Builder连接构建字符串效率最高;
● 带有预初始化的bytes.Buffer和strings.Join这两种方法效率十分接近,分列二三位;
● 未做预初始化的strings.Builder、bytes.Buffer和操作符连接在第三档次;
● fmt.Sprintf性能最差,排在末尾。
由此可以得出一些结论:
● 在能预估出最终字符串长度的情况下,使用预初始化的strings.Builder连接构建字符串效率最高;
● strings.Join连接构建字符串的平均性能最稳定,如果输入的多个字符串是以[]string承载的,那么strings.Join也是不错的选择;
● 使用操作符连接的方式最直观、最自然,在编译器知晓欲连接的字符串个数的情况下,使用此种方式可以得到编译器的优化处理;
● fmt.Sprintf虽然效率不高,但也不是一无是处,如果是由多种不同类型变量来构建特定格式的字符串,那么这种方式还是最适合的。
转换
在前面的例子中,我们看到了string到[]rune以及string到[]byte的转换,这两个转
换也是可逆的,也就是说string和[]rune、[]byte可以双向转换。下面就是从[]rune或
[]byte反向转换为string的例子:
func main() {rs := []rune{0x4E2D,0x56FD,0x6B22,0x8FCE,0x60A8,}s := string(rs)fmt.Println(s)sl := []byte{0xE4, 0xB8, 0xAD,0xE5, 0x9B, 0xBD,0xE6, 0xAC, 0xA2,0xE8, 0xBF, 0x8E,0xE6, 0x82, 0xA8,}s = string(sl)fmt.Println(s)
}
$go run string_slice_to_string.go
中国欢迎您
中国欢迎您
无论是string转slice还是slice转string,转换都是要付出代价的,这些代价的根源
在于string是不可变的,运行时要为转换后的类型分配新内存。我们以byte slice与
string相互转换为例,看看转换过程的内存分配情况:
func byteSliceToString() {
sl := []byte{
0xE4, 0xB8, 0xAD,
0xE5, 0x9B, 0xBD,
0xE6, 0xAC, 0xA2,
0xE8, 0xBF, 0x8E,
0xE6, 0x82, 0xA8,
0xEF, 0xBC, 0x8C,
0xE5, 0x8C, 0x97,
0xE4, 0xBA, 0xAC,
0xE6, 0xAC, 0xA2,
0xE8, 0xBF, 0x8E,0xE6, 0x82, 0xA8,
}
_ = string(sl)
}
func stringToByteSlice() {
s := "中国欢迎您,北京欢迎您"
_ = []byte(s)
}
func main() {
fmt.Println(testing.AllocsPerRun(1, byteSliceToString))
fmt.Println(testing.AllocsPerRun(1, stringToByteSlice))
}
运行这个例子:
$go run string_mallocs_in_convert.go
1
1
我们看到,针对“中国欢迎您,北京欢迎您”这个长度的字符串,在string与byte
slice互转的过程中都要有一次内存分配操作。
在Go运行时层面,字符串与rune slice、byte slice相互转换对应的函数如下:
slicebytetostring: []byte -> string
slicerunetostring: []rune -> string
stringtoslicebyte: string -> []byte
stringtoslicerune: string -> []rune
以byte slice为例,看看slicebytetostring和stringtoslicebyte的实现:
const tmpStringBufSize = 32
type tmpBuf [tmpStringBufSize]byte
func stringtoslicebyte(buf *tmpBuf, s string) []byte {
var b []byte
if buf != nil && len(s) <= len(buf) {
*buf = tmpBuf{}
b = buf[:len(s)]
} else {
b = rawbyteslice(len(s))
}
copy(b, s)
return b
}func slicebytetostring(buf *tmpBuf, b []byte) (str string) {l := len(b)if l == 0 {return ""}// 此处省略一些代码if l == 1 {stringStructOf(&str).str = unsafe.Pointer(&staticbytes[b[0]])stringStructOf(&str).len = 1return}var p unsafe.Pointerif buf != nil && len(b) <= len(buf) {p = unsafe.Pointer(buf)} else {p = mallocgc(uintptr(len(b)), nil, false)}stringStructOf(&str).str = pstringStructOf(&str).len = len(b)memmove(p, (*(*slice)(unsafe.Pointer(&b))).array, uintptr(len(b)))return
}
想要更高效地进行转换,唯一的方法就是减少甚至避免额外的内存分配操作。我们看
到运行时实现转换的函数中已经加入了一些避免每种情况都要分配新内存操作的优化(如
tmpBuf的复用)。
slice类型是不可比较的,而string类型是可比较的,因此在日常Go编码中,我们会经
常遇到将slice临时转换为string的情况。Go编译器为这样的场景提供了优化。在运行时中
有一个名为slicebytetostringtmp的函数就是协助实现这一优化的:
func slicebytetostringtmp(b []byte) string {if raceenabled && len(b) > 0 {racereadrangepc(unsafe.Pointer(&b[0]),uintptr(len(b)),getcallerpc(),funcPC(slicebytetostringtmp))}if msanenabled && len(b) > 0 {msanread(unsafe.Pointer(&b[0]), uintptr(len(b)))}return *(*string)(unsafe.Pointer(&b))
}
该函数的“秘诀”就在于不为string新开辟一块内存,而是直接使用slice的底层存
储。当然使用这个函数的前提是:在原slice被修改后,这个string不能再被使用了。因此
这样的优化是针对以下几个特定场景的。
1)string(b)用在map类型的key中
b := []byte{'k', 'e', 'y'}
m := make(map[string]string)
m[string(b)] = "value"
m[[3]string{string(b), "key1", "key2"}] = "value1"
(2)string(b)用在字符串连接语句中
b := []byte{'t', 'o', 'n', 'y'}
s := "hello " + string(b) + "!"
(3)string(b)用在字符串比较中
s := "tom"
b := []byte{'t', 'o', 'n', 'y'}
if s < string(b) {
...
}
Go编译器对用在for-range循环中的string到[]byte的转换也有优化处理,它不会为
[]byte进行额外的内存分配,而是直接使用string的底层数据。
看下面的例子:
func convert() {
s := "中国欢迎您,北京欢迎您"
sl := []byte(s)
for _, v := range sl {
_ = v
}
}
func convertWithOptimize() {
s := "中国欢迎您,北京欢迎您"
for _, v := range []byte(s) {
_ = v
}
}
func main() {
fmt.Println(testing.AllocsPerRun(1, convert))
fmt.Println(testing.AllocsPerRun(1, convertWithOptimize))
}
运行这个例子程序:
$go run string_for_range_covert_optimize.go
1
0
从结果我们看到,convertWithOptimize函数将string到[]byte的转换放在for-range
循环中,Go编译器对其进行了优化,节省了一次内存分配操作。
相关文章:
go基础10 -字符串的高效构造与转换
前面提到过,Go原生支持通过/操作符来连接多个字符串以构造一个更长的字符串,并且通过/操作符的字符串连接构造是最自然、开发体验最好的一种。 但Go还提供了其他一些构造字符串的方法,比如: ● 使用fmt.Sprintf; ● 使…...
VR钢铁实训 | 铁前事业部虚拟仿真培训软件
随着科技的发展,虚拟现实技术在各个行业中的应用越来越广泛。在钢铁冶炼行业中,VR技术也逐渐得到了应用,其中铁前事业部虚拟仿真培训软件就是一项非常有优势的技术。 铁前事业部虚拟仿真培训软件是广州华锐互动打造的《钢铁生产VR虚拟培训系统…...
DevOps
DevOps 是开发 (Dev) 和运营 (Ops) 的复合词,它将人、流程和技术结合起来,不断地为客户提供价值。 DevOps 对团队意味着什么? DevOps 使以前孤立的角色(开发、IT 运营、质量工程和安全)可以协调和协作,以生…...
IJ中PHP环境的搭建和使用教程
目录 目录 前言 思维导图 1,PHP环境下载 1.下载链接 2.进行安装 3,自定义路径 4.进行相关的一些库的选择下载 2,进行IJ中PHP环境的配置 2.1,下载PHP插件 2.2,下载过程中的注意事项 3,为什么这么做呢? 3.1,原因 3.2,进行代码…...
java开发之个人微信的二次开发
简要描述: 修改我在某群的昵称 请求URL: http://域名/updateIInChatRoomNickName 请求方式: POST 请求头Headers: Content-Type:application/jsonAuthorization:login接口返回 参数: 参…...
ISYSTEM调试实践11-Profiler Timeline和软件运行时间分析
一 前言 本文主要内容是讨论嵌入式软件的时间分析,供大家探讨,如果有疑问欢迎探讨。 对于汽车软件,往往对执行的时序和代码运行的时间有着严格要求。对于在主循环内执行的任务函数,不论是手写还是Autosar生成,能否节拍执行到&…...
第十八章 ObjectScript - 使用例程
文章目录 第十八章 ObjectScript - 使用例程例程Procedures, Functions, and Subroutines 过程、函数和子程序procedurefunctionsubroutine 第十八章 ObjectScript - 使用例程 例程 可以将例程视为 ObjectScript 程序。例程可以从头开始编写,也可以在编译类时自动…...
Linux中select poll和epoll的区别
在Linux Socket服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select、poll和epoll是Linux API提供的I/O复用方式,自从Linux 2.6中加入了epoll之后,在高性能服务器领域得到广泛的应用࿰…...
新知同享|Cloud 开发加速创新,更加安全
谷歌在云平台中深度集成了生成式 AI 功能 帮助出海企业及开发者 轻松借力 AI 推动和实现创新 一起来看 2023 Google 开发者大会上 Google Cloud 如何加速创新,加强信息安全 关注 Google Cloud 的 3 个 AI 重点发展领域 了解生成式 AI 功能如何助推创意落地 精彩大会…...
el-form内容重置(解决点击保存关闭后再点击新增会有编辑携带的数据的问题)
主要代码: this.$refs[ruleForm].resetFields() <template><div class"add-edit-coupon"><el-dialog title"商品优惠券" top"10vh" :visible.sync"dialogVisible" width"660px" :before-close&…...
怎样吃透一个java项目?
前言 对于刚开始看视频敲代码,最忌讳的便是一上来就完全照着视频做,这么做就算完完全全的跟着视频做出来一个项目,始终都无法将里面具体的知识化为己有,单纯来说只是简单的复刻,视频的作者本身是不会对他在做该项目过…...
linux 网络命令
网络命令 配置ip 配置ip有两种方式 #方式一#setup可以使用配置工具进行配置setup#方式二 linux服务器默认网卡配置文件的目录/etc/sysconfig/network-scripts,进行配置网卡的配置文件类型- ifcfg-ethX 有线网卡的配置文件,eth0表示第一块网卡,eth1表示第…...
QUIC协议科普导入(一)
一:QUIC协议导入 QUIC是一个通用的传输层网络协议,最初由Google的Jim Roskind设计,2012年实现并部署,2013年随着实验范围的扩大而公开发布,并向IETF描述。虽然长期处于互联网草案阶段,但在从Chrome浏览器到…...
matlab 矩阵逆运算的条件数
目录 一、概述1、算法概述2、主要函数3、参考文献二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 1、算法概述 条件数法是目前应用最为广泛的一种病态诊断方法。一个方阵…...
[构建自己的 Vue 组件库] 小尾巴 UI 组件库
文章归档于:https://www.yuque.com/u27599042/row3c6 组件库地址 npm:https://www.npmjs.com/package/xwb-ui?activeTabreadme小尾巴 UI 组件库源码 gitee:https://gitee.com/tongchaowei/xwb-ui小尾巴 UI 组件库测试代码 gitee:…...
由于电脑出现msvcr110.dll提示错误的解决方法
最近,我在尝试运行一款新的软件时,突然遇到了一个错误提示,提示说缺少msvcr110.dll文件,导致软件无法启动。在使用电脑过程中,我们常常会遇到一些系统文件丢失的问题。其中,msvcr110.dll是Windows操作系统中…...
[LINUX使用] iptables tcpdump wireshark tshark
iptables: 收到来自 10.10.10.10 的数据后都丢弃 iptables -I INPUT -s 10.10.10.10 -j DROP 直接 reject 来自 10.10.10.* 网段的数据 iptables -I INPUT -s 10.10.10.0/24 -j REJECT tcpdump: dump eth0的数据到本地 tcpdump -i eth0 -w dump.pcap 只抓 目的地址是 10…...
显示器鼠标滚动时或者拖拽文字变为绿色
新电脑,新显示器,看文章时滚动鼠标滑轮,文字颜色就变为绿色。 拖住文本文档或者浏览器等有文字的窗口,文字也会变为绿色。 静止时一点儿问题没有。 以下视频展示滚动和拖拽的操作,视频看不出变色,只参考…...
高校网络安全体系建设及零信任安全架构应用的探索
网络安全是高校信息化建设的重中之重,它同时也随着高校信息化的快速发展而不断面临新的挑战。因此,要用发展的眼光去看待网络安全,体系化推进网络安全体系建设。山东师范大学校园信息化经过10多年的建设发展,在网络安全上探索出了…...
Android Studio设置
Android Studio设置 一、主题 1.下载插件并重启 Material Theme UI 2.下载插件ChroMATERIAL 3.设置主题File >> Settings >> Editor >> Colors Scheme >> Scheme >> ChroMATERIAL 4.设置logcat色值File → Settings → Editor → Colors Schem…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...
