這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
某個午後逛Golang中國社區,看到一個問題
一個簡單的字串效能測試
憑著對一些語言的淺薄瞭解,稍微回答了下:
"在Java中也得到相同的結果.
Go/Java/C#這類靜態語言中,String類型的value是不可變的.每次對字串的”+”操作,都需要重新複製一遍原字串.
所以這些語言涉及到對長字串的操作,都不推薦使用”+”,而是類似Join或者切片之類的東西."
但是後來才想起,Python中的String類似也是不可變的.這麼一來為什麼我的回答就錯了.
既然String類型不可變,在Python中勢必會產生新的對象.可是為什麼速度那麼快 ?
在Google搜了一圈找不到可用的資訊,去看源碼又不知道入口在哪.於是只能上StackOverflow提問了.
What is the different from string's “+” operation between Golang and Python?
目前為止StackOverflow還沒有一個回答,但熱心網友對該問題的評論已經給出了很多重要的資訊.
順著他們給出的資訊,加上我自己的探索,做出如下的總結:
在Python中,對字串的'+='操作,是被最佳化過的
在Python中String顯然是不可變類型,但解譯器(指CPython,下同)遇到形如 str_x += str_y 或者 str_x = str_x + str_y的運算式,還是會取巧得改變String的值,但要求很嚴格:左值沒有被其他運算式引用,且滿足二元操作.
s = ''for i in range(100000): s += 'test' # or s = s + 'test'#Out: 0:00:00.019121
但是,若不滿足二元操作或被其他運算式引用,即像這樣:
str_x = str_x + str_y + str_z
或
str_x += str_x + str_y + str_z
或
str_x = str_x + str_ystr_a += str_x#(↑迴圈10w次飆完了我的記憶體)
解譯器就不會進行最佳化.
還是建議不要使用'+='操作,應該使用join函數.
儘管這樣寫有可能讓代碼看起來更簡潔,但一留神就容易出錯.當資料量非常大時,就等著哭吧,而且有可能因為不斷進行新對象的建立,又被其他運算式引用導致無法被GC清除掉,一下子彪完你的記憶體,上面就是一個例子.
Go的最佳化方法
Go或Java這類語言中String類型是嚴格不變的,不會有類似Python的取巧最佳化.下面給出兩個來自StackOverflow的Go字串拼接的正確姿勢:
- 使用bytes.Buffer類型:
var buffer bytes.Bufferfor n := 0; n < 100000; n++ { buffer.WriteString("test")}
- 使用copy函數
bs := make([]byte, 100000)bl := 0for n := 0; n < 100000; n++ { bl += copy(bs[bl:], "test")}
方法二是效率是方法一的15倍左右,而方法一是"+="操作的10w倍.