最近我在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) }}
新聞熱點
疑難解答