【Go】高效截取字符串_go 字符串截取_CV大使的博客-CSDN博客


本站和网页 https://blog.csdn.net/timczm/article/details/115489622 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

【Go】高效截取字符串_go 字符串截取_CV大使的博客-CSDN博客
【Go】高效截取字符串
CV大使
于 2021-04-07 16:56:39 发布
5076
收藏
分类专栏:
Golang
文章标签:
go
字符串
原文链接:https://blog.thinkeridea.com/201910/go/efficient_string_truncation.html
版权
Golang
专栏收录该内容
4 篇文章
0 订阅
订阅专栏
最近我在 Go Forum 中发现了 [SOLVED] String size of 20 character 的问题,“hollowaykeanho” 给出了相关的答案,而我从中发现了截取字符串的方案并非最理想的方法,因此做了一系列实验并获得高效截取字符串的方法,这篇文章将逐步讲解我实践的过程。
字节切片截取
这正是 “hollowaykeanho” 给出的第一个方案,我想也是很多人想到的第一个方案,利用 go 的内置切片语法截取字符串:
s := "abcdef"
fmt.Println(s[1:4])
我们很快就了解到这是按字节截取,在处理 ASCII 单字节字符串截取,没有什么比这更完美的方案了,中文往往占多个字节,在 utf8 编码中是3个字节,如下程序我们将获得乱码数据:
s := "Go 语言"
fmt.Println(s[1:4])
杀手锏 - 类型转换 []rune
“hollowaykeanho” 给出的第二个方案就是将字符串转换为 []rune,然后按切片语法截取,再把结果转成字符串。
s := "Go 语言"
rs := []rune(s)
fmt.Println(strings(rs[1:4]))
首先我们得到了正确的结果,这是最大的进步。不过我对类型转换一直比较谨慎,我担心它的性能问题,因此我尝试在搜索引擎和各大论坛查找答案,但是我得到最多的还是这个方案,似乎这已经是唯一的解。
我尝试写个性能测试评测它的性能:
package benchmark
import (
"testing"
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrRunes(s string, length int) string {
if utf8.RuneCountInString(s) > length {
rs := []rune(s)
return string(rs[:length])
return s
func BenchmarkSubStrRunes(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRunes(benchmarkSubString, benchmarkSubStringLength)
我得到了让我有些吃惊的结果:
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRunes-8 872253 1363 ns/op 336 B/op 2 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 2.120s
对 69 个的字符串截取前 20 个字符需要大概 1.3 微秒,这极大的超出了我的心里预期,我发现因为类型转换带来了内存分配,这产生了一个新的字符串,并且类型转换需要大量的计算。
救命稻草 - utf8.DecodeRuneInString
我想改善类型转换带来的额外运算和内存分配,我仔细的梳理了一遍 strings 包,发现并没有相关的工具,这时我想到了 utf8 包,它提供了多字节计算相关的工具,实话说我对它并不熟悉,或者说没有主动(直接)使用过它,我查看了它所有的文档发现 utf8.DecodeRuneInString 函数可以转换单个字符,并给出字符占用字节的数量,我尝试了如此下的实验:
package benchmark
import (
"testing"
"unicode/utf8"
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrDecodeRuneInString(s string, length int) string {
var size, n int
for i := 0; i < length && n < len(s); i++ {
_, size = utf8.DecodeRuneInString(s[n:])
n += size
return s[:n]
func BenchmarkSubStrDecodeRuneInString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrDecodeRuneInString(benchmarkSubString, benchmarkSubStringLength)
运行它之后我得到了令我惊喜的结果:
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrDecodeRuneInString-8 10774401 105 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.250s
较 []rune 类型转换效率提升了 13 倍,消除了内存分配,它的确令人激动和兴奋,我迫不及待的回复了 “hollowaykeanho” 告诉他我发现了一个更好的方法,并提供了相关的性能测试。
我有些小激动,兴奋的浏览着论坛里各种有趣的问题,在查看一个问题的帮助时 (忘记是哪个问题了-_-||) ,我惊奇的发现了另一个思路。
良药不一定苦 - range 字符串迭代
许多人似乎遗忘了 range 是按字符迭代的,并非字节。使用 range 迭代字符串时返回字符起始索引和对应的字符,我立刻尝试利用这个特性编写了如下用例:
package benchmark
import (
"testing"
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrRange(s string, length int) string {
var n, i int
for i = range s {
if n == length {
break
n++
return s[:i]
func BenchmarkSubStrRange(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRange(benchmarkSubString, benchmarkSubStringLength)
我尝试运行它,这似乎有着无穷的魔力,结果并没有令我失望。
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRange-8 12354991 91.3 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.233s
它仅仅提升了13%,但它足够的简单和易于理解,这似乎就是我苦苦寻找的那味良药。
如果你以为这就结束了,不、这对我来只是探索的开始。
终极时刻 - 自己造轮子
喝了 range 那碗甜的腻人的良药,我似乎冷静下来了,我需要造一个轮子,它需要更易用,更高效。
于是乎我仔细观察了两个优化方案,它们似乎都是为了查找截取指定长度字符的索引位置,如果我可以提供一个这样的方法,是否就可以提供用户一个简单的截取实现 s[:strIndex(20)],这个想法萌芽之后我就无法再度摆脱,我苦苦思索两天来如何来提供易于使用的接口。
之后我创造了 exutf8.RuneIndexInString 和 exutf8.RuneIndex 方法,分别用来计算字符串和字节切片中指定字符数量结束的索引位置。
我用 exutf8.RuneIndexInString 实现了一个字符串截取测试:
package benchmark
import (
"testing"
"unicode/utf8"
"github.com/thinkeridea/go-extend/exunicode/exutf8"
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrRuneIndexInString(s string, length int) string {
n, _ := exutf8.RuneIndexInString(s, length)
return s[:n]
func BenchmarkSubStrRuneIndexInString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRuneIndexInString(benchmarkSubString, benchmarkSubStringLength)
尝试运行它,我对结果感到十分欣慰:
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRuneIndexInString-8 13546849 82.4 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.213s
性能较 range 提升了 10%,让我很欣慰可以再次获得新的提升,这证明它是有效的。
它足够的高效,但是却不够易用,我截取字符串需要两行代码,如果我想截取 10~20之间的字符就需要4行代码,这并不是用户易于使用的接口,我参考了其它语言的 sub_string 方法,我想我应该也设计一个这个样的接口给用户。
exutf8.RuneSubString 和 exutf8.RuneSub 是我认真思索后编写的方法:
func RuneSubString(s string, start, length int) string
它有三个参数:
s : 输入的字符串start : 开始截取的位置,如果 start 是非负数,返回的字符串将从 string 的 start 位置开始,从 0 开始计算。例如,在字符串 “abcdef” 中,在位置 0 的字符是 “a”,位置 2 的字符串是 “c” 等等。 如果 start 是负数,返回的字符串将从 string 结尾处向前数第 start 个字符开始。 如果 string 的长度小于 start,将返回空字符串。length:截取的长度,如果提供了正数的 length,返回的字符串将从 start 处开始最多包括 length 个字符(取决于 string 的长度)。 如果提供了负数的 length,那么 string 末尾处的 length 个字符将会被省略(若 start 是负数则从字符串尾部算起)。如果 start 不在这段文本中,那么将返回空字符串。 如果提供了值为 0 的 length,返回的子字符串将从 start 位置开始直到字符串结尾。
我为他们提供了别名,根据使用习惯大家更倾向去 strings 包寻找这类问题的解决方法,我创建了exstrings.SubString 和 exbytes.Sub 作为更易检索到的别名方法。
最后我需要再做一个性能测试,确保它的性能:
package benchmark
import (
"testing"
"github.com/thinkeridea/go-extend/exunicode/exutf8"
var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
var benchmarkSubStringLength = 20
func SubStrRuneSubString(s string, length int) string {
return exutf8.RuneSubString(s, 0, length)
func BenchmarkSubStrRuneSubString(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrRuneSubString(benchmarkSubString, benchmarkSubStringLength)
运行它,不会让我失望:
goos: darwin
goarch: amd64
pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark
BenchmarkSubStrRuneSubString-8 13309082 83.9 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark 1.215s
虽然相较 exutf8.RuneIndexInString 有所下降,但它提供了易于交互和使用的接口,我认为这应该是最实用的方案,如果你追求极致仍然可以使用 exutf8.RuneIndexInString,它依然是最快的方案。
总结
当看到有疑问的代码,即使它十分的简单,依然值得深究,并不停的探索它,这并不枯燥和乏味,反而会有极多收获。
从起初 []rune 类型转换到最后自己造轮子,不仅得到了16倍的性能提升,我还学习了utf8包、加深了 range 遍历字符串的特性 以及为 go-extend 仓库收录了多个实用高效的解决方案,让更多 go-extend 的用户得到成果。
go-extend 是一个收录实用、高效方法的仓库,读者们如果好的函数和通用高效的解决方案,期待你们不吝啬给我发送 Pull request,你也可以使用这个仓库加快功能实现及提升性能。
本文作者: 戚银(thinkeridea) 本文链接: https://blog.thinkeridea.com/201910/go/efficient_string_truncation.html 版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处!
CV大使
关注
关注
点赞
收藏
知道了
评论
【Go】高效截取字符串
本文作者: 戚银(thinkeridea)本文链接: https://blog.thinkeridea.com/201910/go/efficient_string_truncation.html版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处!最近我在 Go Forum 中发现了 [SOLVED] String size of 20 character 的问题,“hollowaykeanho” 给出了相关的答案,而我从中发现了截取字符串的方案.
复制链接
扫一扫
专栏目录
C#中截取字符串的的基本方法详解
12-17
分享几个经常用到的字符串的截取 string str="123abc456"; int i=3; 1 取字符串的前i个字符 str=str.Substring(0,i); // orstr=str.Remove(i,str.Length-i); 2 去掉字符串的前i个字符: str=str.Remove(0,i); //...
go语言按显示长度截取字符串的方法
09-22
主要介绍了go语言按显示长度截取字符串的方法,涉及Go语言操作字符串的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
参与评论
您还未登录,请先
登录
后发表或查看评论
vue如何截取字符串
01-21
在后端有许多的封装方法来截取字符串或者对字符串的操作,同样前端也有相应的方法。
有一个data数据为ipaddr
data() {
return {
ipaddr: 192.168.100.110,
};
},
1、现在需要切割成一个数组,得到四个ip值,只需要通过计算属性就可以实现
computed:{
ipaddrArray:function(){
return this.ipaddr.split('.')
最终通过ul可以展现切割的字符串
<li v-for=item in i
go 高效按照字符切割字符串
zhu0902150102的博客
03-09
56
go 高效按照字符切割字符串
Go语言截取字符串函数用法
09-22
主要介绍了Go语言截取字符串函数用法,实例分析了Go语言操作字符串的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
VUE 截取字符串的方法
07-30
适合刚刚进入(JavaScript,Vue)编程领域的小白使用
JAVA如何按字节截取字符串
08-25
主要介绍了JAVA如何按字节截取字符串,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
go 字符串截取
是鲤鱼啊
09-10
1971
【代码】go 字符串截取。
c++ primer plus笔记(13)string类
走过的路
07-21
119
string类包含于<string>头文件中。
一、构造方法:
string s("..."); //...为c-stlye-string
string s(20, '.'); //20个.
string s(chr, 20); //(char*)chr,chr的前20个字符
string s(p1, p2); //[p1, p2)地...
go 语言字符串常见方法
yuelai_217的博客
02-08
613
go 语言字符串常见方法
Go语言中切片是如何被截取的?
nxj_climb的博客
09-03
924
截取也是创建slice的方法,可以从数组或则slice直接截取,需要指定起,止索引位置。基于已有的slice创建新的slice对象,被称为 reslice。新的slice 和老的slice工用底层数组,新老slice对底层数组的更改都会影响到彼此。基于数组创建新的slice也是同样的效果,对数组或slice元素的更改都会影响到彼此。
go字符串截取笔记
weixin_34161064的博客
02-18
332
为什么80%的码农都做不了架构师?>>>
...
Go 高效截取字符串的一些思考
09-18
主要介绍了Go 高效截取字符串的一些思考,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
Go语言按字节截取字符串的方法
09-22
主要介绍了Go语言按字节截取字符串的方法,涉及Go语言操作字符串的技巧,非常具有实用价值,需要的朋友可以参考下
JS截取字符串常用方法详细整理
10-26
截取字符串的使用比较广泛,有很多中方法,本文粗略的整理了一些,感兴趣的额朋友可以才参考下
SQL 截取字符串应用代码
09-11
字符串截取函数,只限单字节字符使用(对于中文的截取时遇上奇数长度是会出现乱码,需另行处理),本函数可截取字符串指定范围内的字符。
[golang] golang实现截取字符串函数SubStr
小红帽
06-30
3369
golang中没有很多其他语言中的截取字符串函数
现在单独定义一个函数来处理
// 截取字符串,支持多字节字符
// start:起始下标,负数从从尾部开始,最后一个为-1
// length:截取长度,负数表示截取到末尾
func SubStr(str string, start int, length int) (result string) {
s := []rune(str)
...
Go语言实现字符串截取
总想试试,万一成了呢??
03-20
4714
Go语言没有像Java一样的substring()方法,但是可以通过如下方式实现字符串截取
func Test_GoSubString(t *testing.T) {
str := "sssssddddd"
rs := []rune(str)
// rs[开始索引:结束索引]
fmt.Println(string(rs[3:6]))
str = "你好, Go语言"
rs = []ru...
Go语言入门之字符串的截取和常用函数
MX
10-31
6859
1、字符串截取介绍
可以使用len(字符串变量)获取字符串的字节长度,其中英文占1个字节长度,中文占3个字节长度。(这是因为在Golang中string类型的底层是通过byte数组实现的,在unicode编码中,中文字符占两个字节,而在utf-8编码中,中文字符占三个字节而Golang的默认编码正是utf-8。)举例如下:
可以使用变量名[n]获取字符串的第n+1个字节,返回这个字节对应的U...
shell 截取字符串
最新发布
03-16
可以使用 `cut` 命令来截取字符串。例如:
```
$ echo "hello world" | cut -c 1-5
hello
```
该命令表示将字符串 "hello world" 截取前 5 个字符,即 "hello"。
也可以使用 `sed` 命令来截取字符串。例如:
```
$ echo "hello world" | sed 's/\(^.\{5\}\).*/\1/'
hello
```
该命令表示将字符串 "hello world" 从开头截取 5 个字符,即 "hello"。
还可以使用 `awk` 命令来截取字符串。例如:
```
$ echo "hello world" | awk '{print substr($0,1,5)}'
hello
```
该命令表示将字符串 "hello world" 从1开始截取5个字符,即 "hello"。
“相关推荐”对你有帮助么?
非常没帮助
没帮助
一般
有帮助
非常有帮助
提交
CV大使
CSDN认证博客专家
CSDN认证企业博客
码龄5年
北京字节跳动科技有限公司
26
原创
9万+
周排名
76万+
总排名
3万+
访问
等级
505
积分
55
粉丝
93
获赞
25
评论
83
收藏
私信
关注
热门文章
Win10 IPv6 远程桌面连接(小米路由器)
11107
深入理解JVM - 阅读笔记之思维导图 - 目录
5296
【Go】高效截取字符串
5066
计算机专业大学生必备:Docker基础用法
1795
Git出现"Everything up-to-date"引发的一系列问题
1307
分类专栏
Go 从 0 开发 RPC 框架
Golang
4篇
笔记
elasticsearch
4篇
深入理解Java虚拟机
5篇
LeetCode
4篇
计算机网络
算法
1篇
心得
2篇
最新评论
Win10 IPv6 远程桌面连接(小米路由器)
千遇_D:
跳过路由直连光猫电脑拨号上网,防火墙关了还是没ping通v6地址
Win10 IPv6 远程桌面连接(小米路由器)
等jzy:
记得加权 chmod +x /etc/rc.local
Win10 IPv6 远程桌面连接(小米路由器)
-spark-:
好像是小米路由器大部分都是ipv6防火墙封了所有入站,所以不能远程访问。。还没有手动控制ipv6防火墙的设置,太拉了
计算机专业大学生必备:Docker基础用法
小陈运维:
膜拜技术大佬,来我博客指点江山吧
计算机专业大学生必备:Docker基础用法
king config:
哇,好棒啊,崇拜的小眼神 ,已点赞,欢迎回赞,回评哦~~~
您愿意向朋友推荐“博客详情页”吗?
强烈不推荐
不推荐
一般般
推荐
强烈推荐
提交
最新文章
为什么 nil != nil? Go “==“ 机制详解
10分钟上手 Go 函数式编程
Linux解决端口占用
2021年7篇
2020年8篇
2019年9篇
2018年3篇
目录
目录
分类专栏
Go 从 0 开发 RPC 框架
Golang
4篇
笔记
elasticsearch
4篇
深入理解Java虚拟机
5篇
LeetCode
4篇
计算机网络
算法
1篇
心得
2篇
目录
评论
被折叠的 条评论
为什么被折叠?
到【灌水乐园】发言
查看更多评论
添加红包
祝福语
请填写红包祝福语或标题
红包数量
红包个数最小为10个
红包总金额
红包金额最低5元
余额支付
当前余额3.43元
前往充值 >
需支付:10.00元
取消
确定
下一步
知道了
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝
规则
hope_wisdom 发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。 2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。
余额充值