常用拼接方法
字符串拼接在日常開發中是很常見的需求,目前有兩種普遍做法:
一種是直接用 += 來拼接
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的[]byte func BenchmarkSpliceBufferByte10(b *testing.B) { var buff bytes.Buffer for i := 0; i < b.N; i++ { buff.Write(GenRandBytes(10)) } } // BenchmarkSpliceBuilderByte10 測試使用string.Builder拼接N次長度為10的[]byte func 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個隨機字符的string func 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個隨機字符的[]byte func 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,不僅是更好的性能,也是為了能使代碼更清晰。
當然,對於簡單的拼接,+= 就足夠了