常用拼接方法
字元串拼接在日常開發中是很常見的需求,目前有兩種普遍做法:
一種是直接用 += 來拼接
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,不僅是更好的性能,也是為了能使代碼更清晰。
當然,對于簡單的拼接,+= 就足夠了