首页 > 网站 > 帮助中心 > 正文

Go 高效截取字符串的一些思考

2024-07-09 22:41:07
字体:
来源:转载
供稿:网友

最近我在Go Forum 中发现了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 benchmarkimport (  "testing")var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"var benchmarkSubStringLength = 20func 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: darwingoarch: amd64pkg: github.com/thinkeridea/go-extend/exunicode/exutf8/benchmarkBenchmarkSubStrRunes-8      872253       1363 ns/op       336 B/op     2 allocs/opPASSok   github.com/thinkeridea/go-extend/exunicode/exutf8/benchmark   2.120s

对 69 个的字符串截取前 20 个字符需要大概 1.3 微秒,这极大的超出了我的心里预期,我发现因为类型转换带来了内存分配,这产生了一个新的字符串,并且类型转换需要大量的计算。

救命稻草 - utf8.DecodeRuneInString

我想改善类型转换带来的额外运算和内存分配,我仔细的梳理了一遍 strings 包,发现并没有相关的工具,这时我想到了 utf8 包,它提供了多字节计算相关的工具,实话说我对它并不熟悉,或者说没有主动(直接)使用过它,我查看了它所有的文档发现 utf8.DecodeRuneInString 函数可以转换单个字符,并给出字符占用字节的数量,我尝试了如此下的实验:

package benchmarkimport (  "testing"  "unicode/utf8")var benchmarkSubString = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"var benchmarkSubStringLength = 20func 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)  }}
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表