這是一片關于stackoverflow熱門問題的文章 How to efficiently concatenate strings
Go里面string是最基礎的類型,是一個只讀類型,針對他的每一個操作都會創建一個新的string
所以,如果我在不知道結果是多少長字符串的情況下不斷的連接字符串,怎么樣的方式是最好的呢?
1. 方法一:使用strings.Builder
從Go 1.10(2018)版本開始可以使用 strings.Builder ,
A Builder is used to efficiently build a string using Write methods. It minimizes memory copying.
strings.Builder 使用 Write 方法來高效的構造字符串. 它使用內存最小,它使用零值,它不拷貝零值.
注意: 不要拷貝strings.Builder的值,如果你要使用strings.Builder值請使用pointer
使用方法,代碼如下:
package main import ( "strings" "fmt" ) func main() { var str strings.Builder for i := 0; i < 1000; i++ { str.WriteString("a") } fmt.Println(str.String()) }
2. 方法二:使用bytes.Buffer
在201X年之前使用 bytes 包的 Buffer 它實現了 io.Writer 的接口,使用他來拼接字符串.他的事件復雜度 O(n) .
package main import ( "bytes" "fmt" ) func main() { var buffer bytes.Buffer for i := 0; i < 1000; i++ { buffer.WriteString("a") } fmt.Println(buffer.String()) }
3. 方法三:使用Go語言內置函數copy
Go內建函數copy: func copy(dst, src []Type) int ,
用于將源slice的數據(第二個參數),復制到目標slice(第一個參數).
返回值為拷貝了的數據個數,是len(dst)和len(src)中的最小值.
package main import ( "bytes" "fmt" ) func main() { bs := make([]byte, 1000) bl := 0 for n := 0; n < 1000; n++ { bl += copy(bs[bl:], "a") } fmt.Println(string(bs)) }
4. 方法四:使用go語言內置函數Append
append主要用于給某個切片(slice)追加元素,
如果該切片存儲空間(cap)足夠,就直接追加,長度(len)變長;如果空間不足,就會重新開辟內存,并將之前的元素和新的元素一同拷貝進去,
第一個參數為切片,后面是該切片存儲元素類型的可變參數,
package main import ( "bytes" "fmt" ) func main() { bs := make([]byte, 1000) for n := 0; n < 1000; n++ { bs = append(bs,'a') } fmt.Println(string(bs)) }
5. 方法五: 使用字符串+運算
package main import ( "fmt" ) func main() { var result string for i := 0; i < 1000; i++ { result += "a" } fmt.Println(result) }
6. 方法六: strings.Repeat
strings.Repeat 將 count 個字符串 s 連接成一個新的字符串
package main import ( "fmt" "strings" ) func main() { fmt.Println(strings.Repeat("x",1000)) }
strings.Repeat它的底層調用的是strings.Builder,提前分配了內存.
// Repeat returns a new string consisting of count copies of the string s. // // It panics if count is negative or if // the result of (len(s) * count) overflows. func Repeat(s string, count int) string { if count == 0 { return "" } // Since we cannot return an error on overflow, // we should panic if the repeat will generate // an overflow. // See Issue golang.org/issue/16237 if count < 0 { panic("strings: negative Repeat count") } else if len(s)*count/count != len(s) { panic("strings: Repeat count causes overflow") } n := len(s) * count var b Builder b.Grow(n) b.WriteString(s) for b.Len() < n { if b.Len() <= n/2 { b.WriteString(b.String()) } else { b.WriteString(b.String()[:n-b.Len()]) break } } return b.String() }
7. Benchmark
string_benchmark.go
package main import ( "bytes" "strings" "testing" ) const ( sss = "https://mojotv.cn" cnt = 10000 ) var ( bbb = []byte(sss) expected = strings.Repeat(sss, cnt) ) //使用 提前初始化 內置 copy函數 func BenchmarkCopyPreAllocate(b *testing.B) { var result string for n := 0; n < b.N; n++ { bs := make([]byte, cnt*len(sss)) bl := 0 for i := 0; i < cnt; i++ { bl += copy(bs[bl:], sss) } result = string(bs) } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } //使用 提前初始化 內置append 函數 func BenchmarkAppendPreAllocate(b *testing.B) { var result string for n := 0; n < b.N; n++ { data := make([]byte, 0, cnt*len(sss)) for i := 0; i < cnt; i++ { data = append(data, sss...) } result = string(data) } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } //使用 提前初始化 bytes.Buffer func BenchmarkBufferPreAllocate(b *testing.B) { var result string for n := 0; n < b.N; n++ { buf := bytes.NewBuffer(make([]byte, 0, cnt*len(sss))) for i := 0; i < cnt; i++ { buf.WriteString(sss) } result = buf.String() } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } //使用 strings.Repeat 本質是pre allocate + strings.Builder func BenchmarkStringRepeat(b *testing.B) { var result string for n := 0; n < b.N; n++ { result = strings.Repeat(sss,cnt) } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } //使用 內置copy func BenchmarkCopy(b *testing.B) { var result string for n := 0; n < b.N; n++ { data := make([]byte, 0, 64) // same size as bootstrap array of bytes.Buffer for i := 0; i < cnt; i++ { off := len(data) if off+len(sss) > cap(data) { temp := make([]byte, 2*cap(data)+len(sss)) copy(temp, data) data = temp } data = data[0 : off+len(sss)] copy(data[off:], sss) } result = string(data) } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } //使用 內置append func BenchmarkAppend(b *testing.B) { var result string for n := 0; n < b.N; n++ { data := make([]byte, 0, 64) for i := 0; i < cnt; i++ { data = append(data, sss...) } result = string(data) } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } //使用 bytes.Buffer func BenchmarkBufferWriteBytes(b *testing.B) { var result string for n := 0; n < b.N; n++ { var buf bytes.Buffer for i := 0; i < cnt; i++ { buf.Write(bbb) } result = buf.String() } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } //使用 strings.Builder write bytes func BenchmarkStringBuilderWriteBytes(b *testing.B) { var result string for n := 0; n < b.N; n++ { var buf strings.Builder for i := 0; i < cnt; i++ { buf.Write(bbb) } result = buf.String() } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } //使用 string buffer write string func BenchmarkBufferWriteString(b *testing.B) { var result string for n := 0; n < b.N; n++ { var buf bytes.Buffer for i := 0; i < cnt; i++ { buf.WriteString(sss) } result = buf.String() } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } } // 使用string 加號 func BenchmarkStringPlusOperator(b *testing.B) { var result string for n := 0; n < b.N; n++ { var str string for i := 0; i < cnt; i++ { str += sss } result = str } b.StopTimer() if result != expected { b.Errorf("unexpected result; got=%s, want=%s", string(result), expected) } }
執行 go test -bench=. -benchmem 輸出結果:
$ go test -bench=. -benchmem goos: windows goarch: amd64 BenchmarkCopyPreAllocate-8 10000 117600 ns/op 344065 B/op 2 allocs/op BenchmarkAppendPreAllocate-8 20000 75300 ns/op 344065 B/op 2 allocs/op BenchmarkBufferPreAllocate-8 20000 97149 ns/op 344065 B/op 2 allocs/op BenchmarkStringRepeat-8 100000 18349 ns/op 172032 B/op 1 allocs/op BenchmarkCopy-8 10000 152417 ns/op 862307 B/op 13 allocs/op BenchmarkAppend-8 10000 157210 ns/op 1046405 B/op 23 allocs/op BenchmarkBufferWriteBytes-8 10000 173207 ns/op 862374 B/op 14 allocs/op BenchmarkStringBuilderWriteBytes-8 10000 155715 ns/op 874468 B/op 24 allocs/op BenchmarkBufferWriteString-8 10000 165700 ns/op 862373 B/op 14 allocs/op BenchmarkStringPlusOperator-8 20 84450010 ns/op 885204590 B/op 10037 allocs/op PASS ok _/D_/code/tech.mojotv.cn/tutorials 18.797s
下面著重解釋下說出的結果,看到函數后面的-8了嗎?這個表示運行時對應的 GOMAXPROCS 的值.
接著的10000表示運行for循環的次數,也就是調用被測試代碼的次數,最后的 174799 ns/op 表示每次需要話費174799納秒.
14 allocs/op 表示每次執行分配了32字節內存.
8. 結論:
如果合并大量重復的字符串請使用 strings.Repeat , 如果要合并不同的字符串,且圖方便建議使用 string.Builder + Write bytes/string .
+
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流