当前位置 主页 > 服务器问题 > Linux/apache问题 >

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

    栏目:Linux/apache问题 时间:2019-11-05 10:37

    最近我在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)
      }
    }