当前位置 主页 > 服务器问题 > Linux/apache问题 >
最近我在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 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) } }