Golang字符串拼接的性能以及原理詳解
1.字符串高效拼接
在go語言中,字符串(string)是不可變的,因此字符串之間的拼接實際上是創(chuàng)建了一個新的字符串。如果頻繁的進行字符串拼接,那將會對性能產(chǎn)生嚴重的影響!
1.1常見的拼接方式
(1)使用 +
func plusConcat(n int, str string) string { s := "" for i := 0; i < n; i++ { s += str } return s }
(2)使用fmt.Sprintf
func sprintfConcat(n int, str string) string { s := "" for i := 0; i < n; i++ { s = fmt.Sprintf("%s%s", s, str) } return s }
(3) 使用strings.Builder
func builderConcat(n int, str string) string { var builder strings.Builder for i := 0; i < n; i++ { builder.WriteString(str) } return builder.String() }
(4) 使用bytes.Buffer
func bufferConcat(n int, str string) string { buffer := new(bytes.Buffer) for i := 0; i < n; i++ { buffer.WriteString(str) } return buffer.String() }
(5) 使用[] byte
func byteConcat(n int, str string) string { buf := make([]byte, 0, n*len(str)) for i := 0; i < n; i++ { buf = append(buf, str...) } return string(buf) }
1.2使用benchmark進行性能對比
測試代碼:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randomString(n int) string { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return string(b) } func benchmark(b *testing.B, f func(int, string) string) { var str = randomString(10) for i := 0; i < b.N; i++ { f(10000, str) } } func BenchmarkPlusConcat(b *testing.B) { benchmark(b, plusConcat) } func BenchmarkSprintfConcat(b *testing.B) { benchmark(b, sprintfConcat) } func BenchmarkBuilderConcat(b *testing.B) { benchmark(b, builderConcat) } func BenchmarkBufferConcat(b *testing.B) { benchmark(b, bufferConcat) } func BenchmarkByteConcat(b *testing.B) { benchmark(b, byteConcat) }
測試結(jié)果:
$ go test -bench="Concat$" -benchmem .
goos: darwin
goarch: amd64
pkg: example
BenchmarkPlusConcat-8 19 56 ms/op 530 MB/op 10026 allocs/op
BenchmarkSprintfConcat-8 10 112 ms/op 835 MB/op 37435 allocs/op
BenchmarkBuilderConcat-8 8901 0.13 ms/op 0.5 MB/op 23 allocs/op
BenchmarkBufferConcat-8 8130 0.14 ms/op 0.4 MB/op 13 allocs/op
BenchmarkByteConcat-8 17379 0.07 ms/op 0.2 MB/op 2 allocs/op
PASS
ok example 8.627s
總結(jié): 通過對比,發(fā)現(xiàn)fmt.sprintf()
和+
的性能最低,和其他方法相比,性能低了差不多1000倍,且占用內(nèi)存也比其他方法高了1000倍;而其他三者的性能和占用內(nèi)存相差不多;性能最高的方法是[]byte,因為提前分配了足夠的內(nèi)存,所以拼接是不會進行字符串的拷貝與內(nèi)存的重新分配,固效果最佳。
1.3字符串拼接最終建議
綜合易用性和性能,一般推薦使用 strings.Builder
來拼接字符串。
官方解釋:
A Builder is used to efficiently build a string using Write methods.
It minimizes memory copying.
如果對 strings.Builder
進行內(nèi)存預(yù)分配,性能還可以再次提升??梢允褂肎row()來對內(nèi)存進行預(yù)分配。
如:
func builderConcat(n int, str string) string { var builder strings.Builder builer.Grow(n*len(str)) for i := 0; i < n; i++ { builder.WriteString(str) } return builder.String() }
2.性能背后的原理
2.1 比較 strings.Builder和 +
strings.Builder 和 + 性能和內(nèi)存消耗差距如此巨大,是因為兩者的內(nèi)存分配方式不一樣。
- 字符串在 Go 語言中是不可變類型,占用內(nèi)存大小是固定的,當(dāng)使用
+
拼接 2 個字符串時,生成一個新的字符串,那么就需要開辟一段新的空間,新空間的大小是原來兩個字符串的大小之和。拼接第三個字符串時,再開辟一段新空間,新空間大小是三個字符串大小之和,以此類推。假設(shè)一個字符串大小為 10 byte,拼接 1w 次,需要申請的內(nèi)存大小為:
10 + 2 * 10 + 3 * 10 + … + 10000 * 10 byte = 500 MB
- 而
strings.Builder
、bytes.Buffer
,包括切片[]byte
的內(nèi)存是以倍數(shù)申請的。例如,初始大小為 0,當(dāng)?shù)谝淮螌懭氪笮?10 byte 的字符串時,則會申請大小為 16 byte 的內(nèi)存(恰好大于 10 byte 的 2 的指數(shù)),第二次寫入 10 byte 時,內(nèi)存不夠,則申請 32 byte 的內(nèi)存,第三次寫入內(nèi)存足夠,則不申請新的,以此類推。在實際過程中,超過一定大小,比如 2048 byte 后,申請策略上會有些許調(diào)整。
2.2 比較 strings.Builder 和 bytes.Buffer
strings.Builder
和 bytes.Buffer
底層都是 []byte 數(shù)組,但 strings.Builder
性能比 bytes.Buffer
略快約 10% 。一個比較重要的區(qū)別在于,bytes.Buffer
轉(zhuǎn)化為字符串時重新申請了一塊空間,存放生成的字符串變量,而 strings.Builder
直接將底層的[]byte
轉(zhuǎn)換成了字符串類型返回了回來。
總結(jié)
到此這篇關(guān)于Golang字符串拼接的性能以及原理詳解的文章就介紹到這了,更多相關(guān)Golang字符串拼接性能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于golang uint8、int8與byte的區(qū)別說明
這篇文章主要介紹了基于golang uint8、int8與byte的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03Go垃圾回收提升內(nèi)存管理效率優(yōu)化最佳實踐
這篇文章主要為大家介紹了Go垃圾回收提升內(nèi)存管理效率優(yōu)化最佳實踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12golang?xorm?自定義日志記錄器之使用zap實現(xiàn)日志輸出、切割日志(最新)
這篇文章主要介紹了golang?xorm?自定義日志記錄器,使用zap實現(xiàn)日志輸出、切割日志,包括連接postgresql數(shù)據(jù)庫的操作方法及?zap日志工具?,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-10-10Go?Excelize?API源碼閱讀SetSheetViewOptions示例解析
這篇文章主要為大家介紹了Go-Excelize?API源碼閱讀SetSheetViewOptions示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08Golang中的new()和make()函數(shù)本質(zhì)區(qū)別
在 Go 語言開發(fā)中,new() 和 make() 是兩個容易讓開發(fā)者感到困惑的內(nèi)建函數(shù),盡管它們都用于內(nèi)存分配,但其設(shè)計目的、適用場景和底層實現(xiàn)存在本質(zhì)差異,本文將通過類型系統(tǒng)、內(nèi)存模型和編譯器實現(xiàn)三個維度,深入解析這兩個函數(shù)的本質(zhì)區(qū)別,感興趣的朋友一起看看吧2025-02-02如何通過Golang的container/list實現(xiàn)LRU緩存算法
文章介紹了Go語言中container/list包實現(xiàn)的雙向鏈表,并探討了如何使用鏈表實現(xiàn)LRU緩存,LRU緩存通過維護一個雙向鏈表來管理數(shù)據(jù),確保在插入和刪除操作時能夠以O(shè)(1)的平均時間復(fù)雜度運行,提供了鏈表的操作和使用場景,并附帶了實現(xiàn)LRU緩存的代碼示例,感興趣的朋友一起看看吧2025-03-03