常用拼接方法
字串拼接在日常開發中是很常見的需求,目前有兩種普遍做法:
一種是直接用 += 來拼接
s1 := "Hello"s2 := "World"s3 := s1 + s2 // s3 == "HelloWorld"s1 += s2 // s1 == "HelloWorld"
這是最常用也是最簡單直觀的方法,不過簡單是有代價的,golang的字串是不可變類型,也就是說每一次對字串的“原地”修改都會重建一個string,再把資料複製進去,這樣一來將會產生很可觀的效能開銷,稍後的效能測試中將會看到這一點。
第二種是使用bytes.Buffer
// bytes.Buffer的0值可以直接使用var buff bytes.Buffer// 向buff中寫入字元/字串buff.Write([]byte("Hello"))buff.WriteByte(' ')buff.WriteString("World")// String() 方法獲得拼接的字串buff.String() // "Hello World"
這種方法用於需要大量進行字串拼接操作的場合,效能要大大優於第一種方法。
不過使用bytes模組來操作string難免讓人產生迷惑,所以在go1.10中新增了第三種方法:strings.Builder,官方鼓勵盡量在string的拼接時使用Builder,byte拼接時使用Buffer
// strings.Builder的0值可以直接使用var builder strings.Builder// 向builder中寫入字元/字串builder.Write([]byte("Hello"))builder.WriteByte(' ')builder.WriteString("World")// String() 方法獲得拼接的字串builder.String() // "Hello World"
從上面的代碼中可以看到,strings.Builder和bytes.Buffer的操作幾乎一樣,不過strings.Builder僅僅實現了write類方法,而Buffer是可讀可寫的。
所以strings.Builder僅用於拼接/構建字串
效能
除了是否易用外,另一條參考標準就是效能,得益於golang內建的測試載入器,我們可以大致對比一下三種方案的效能。
測試使用從26個大寫和小寫字母10個數字以及5個常用符號共67字元中隨機取10個組成string或[]byte,再由Buffer和Builder進行拼接。
先上測試結果
go test -bench=. -benchmem
下面是測試代碼
// BenchmarkSpliceAddString10 測試使用 += 拼接N次長度為10的字串func BenchmarkSpliceAddString10(b *testing.B) { s := "" for i := 0; i < b.N; i++ { s += GenRandString(10) }}// BenchmarkSpliceBuilderString10 測試使用strings.Builder拼接N次長度為10的字串func BenchmarkSpliceBuilderString10(b *testing.B) { var builder strings.Builder for i := 0; i < b.N; i++ { builder.WriteString(GenRandString(10)) }}// BenchmarkSpliceBufferString10 測試使用bytes.Buffer拼接N次長度為10的字串func BenchmarkSpliceBufferString10(b *testing.B) { var buff bytes.Buffer for i := 0; i < b.N; i++ { buff.WriteString(GenRandString(10)) }}// BenchmarkSpliceBufferByte10 測試使用bytes.Buffer拼接N次長度為10的[]bytefunc BenchmarkSpliceBufferByte10(b *testing.B) { var buff bytes.Buffer for i := 0; i < b.N; i++ { buff.Write(GenRandBytes(10)) }}// BenchmarkSpliceBuilderByte10 測試使用string.Builder拼接N次長度為10的[]bytefunc BenchmarkSpliceBuilderByte10(b *testing.B) { var builder strings.Builder for i := 0; i < b.N; i++ { builder.Write(GenRandBytes(10)) }}
這是產生供拼接使用的隨機字串的代碼(這裡仍然使用了bytes.Buffer,推薦使用新的strings.Builder)
const ( data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,.-=/")func init() { rand.Seed(time.Now().Unix()) // 設定隨機種子}// GenRandString 產生n個隨機字元的stringfunc GenRandString(n int) string { max := len(data) var buf bytes.Buffer for i := 0; i < n; i++ { buf.WriteByte(data[rand.Intn(max)]) } return buf.String()}// GenRandBytes 產生n個隨機字元的[]bytefunc GenRandBytes(n int) []byte { max := len(data) buf := make([]byte, n) for i := 0; i < n; i++ { buf[i] = data[rand.Intn(max)] } return buf}
使用 += 的方法效能是最慢的,效能和其他兩種差了好幾個數量級。
Buffer和Builder效能相差無幾,Builder在記憶體的使用上要略優於Buffer
結論
strings.Builder在golang 1.10才引入標準庫的,所以 version <= 1.9 的時候對於大量字串的拼接操作推薦bytes.Buffer
如果你正在使用1.10+,那麼建議使用strings.Builder,不僅是更好的效能,也是為了能使代碼更清晰。
當然,對於簡單的拼接,+= 就足夠了