go字串拼接操作
1. 使用 + 拼接
通過檢視彙編程式碼可知 +
實際上呼叫的是 runtime/string.go中的concatstrings 函式,該函式原始碼如下:
// concatstrings implements a Go string concatenation x+y+z+...
// The operands are passed in the slice a.
// If buf != nil, the compiler has determined that the result does not
// escape the calling function, so the string data can be stored in buf
// if small enough.
func concatstrings(buf *tmpBuf, a []string) string {
idx := 0
l := 0
count := 0
for i, x := range a {
n := len(x)
if n == 0 {
continue
}
if l+n < l { //如果需要拼接的字串太多,其位元組數超過int的最大值,l將變為負數,所以l+n會小於l
throw("string concatenation too long")
}
l += n
count++
idx = i
}
if count == 0 {
return ""
}
// If there is just one string and either it is not on the stack
// or our result does not escape the calling frame (buf != nil),
// then we can return that string directly.
if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
return a[idx]
}
s, b := rawstringtmp(buf, l) //上面計算出所需要的位元組空間,通過copy函式完成拼接
for _, x := range a {
copy(b, x)
b = b[len(x):]
}
return s
}
通過原始碼分析,使用 x+y+z+...
的方式完成一次拼接
將與下面提到的strings.Join等方式沒有太多的效能差異,但是在迴圈中出現效能損耗主要是在記憶體分配方面,因為每一次拼接都需要一次記憶體分配。即在一次拼接時+
效能很好,但多次拼接效能就會變差。注意在最後沒有將[]byte轉換成string的損耗,而strings.Join在最後有將[]byte轉換為string的損耗,故一次拼接其效能要比strings.Join好。
2. strings.Join
// Join concatenates the elements of a to create a single string. The separator string
// sep is placed between elements in the resulting string.
func Join(a []string, sep string) string {
switch len(a) {
case 0:
return ""
case 1:
return a[0]
case 2:
// Special case for common small values.
// Remove if golang.org/issue/6714 is fixed
return a[0] + sep + a[1]
case 3:
// Special case for common small values.
// Remove if golang.org/issue/6714 is fixed
return a[0] + sep + a[1] + sep + a[2]
}
n := len(sep) * (len(a) - 1)
for i := 0; i < len(a); i++ {
n += len(a[i])
}
b := make([]byte, n)
bp := copy(b, a[0])
for _, s := range a[1:] {
bp += copy(b[bp:], sep)
bp += copy(b[bp:], s)
}
return string(b) //注意這裡有轉換的損耗
}
該方法優於+
的地方在於一次性分配所需空間,而+
每一次迭代都需要重新分配。該方法一次呼叫其效能足夠好,但多次呼叫就需要多次分配記憶體,其效能差於下述的bytes.Buffer。
3. bytes.Buffer
原始碼實現在bytes/buffer.go中 小記憶體優化,能提前預分配記憶體,記憶體不足時*2倍增長,但是最後獲取string結果有[]byte轉string的消耗,故bytes.Buffer在一次初始化(提前計算總長度,一次性預分配好記憶體更好),多次字串連線操作,最後一次性獲取string結果的場景中是最快的。靈活性是最強的
byte.Buffer之所以在多次連線操作中效能會更好,是因為當記憶體不夠時會以2倍重新分配,在後面將減少記憶體分配的次數,從而提升效能。
4. fmt.Sprintf
綜上,
如果是少量小文字拼接,用 “+” 就好
如果是大量小文字拼接,用 strings.Join
如果是大量大文字拼接,用 bytes.Buffer
再次綜上
+操作符 通過彙編可知實現在runtime/string.go中, 主要是concatstrings函式 短字串優化,沒有藉助[]byte造成轉換string的消耗,故單次呼叫+操作符是最快的。靈活性最差。
bytes.Buffer 原始碼實現在bytes/buffer.go中 小記憶體優化,能提前預分配記憶體,記憶體不足時*2倍增長,但是最後獲取string結果有[]byte轉string的消耗,故bytes.Buffer在一次初始化(提前計算總長度,一次性預分配好記憶體更好),多次字串連線操作,最後一次性獲取string結果的場景中是最快的。靈活性是最強的
strings.Join 原始碼實現在strings/strings.go中 少量字串連線優化,一次性分配記憶體,有[]byte轉換string的消耗,故單次呼叫能達到bytes.Buffer的最好效果,但是它不夠靈活
fmt.Sprintf 原始碼實現在fmt/print.go中 因為a…interface{}有引數轉換的消耗, 藉助[]byte每次新增呼叫append,邏輯相對複雜,最後獲取結果有[]byte轉string的消耗,故fmt.Sprintf一般要慢於bytes.Buffer和strings.Join,靈活性和strings.Join差不多
結論
單次呼叫效能:操作符+>strings.Join>=bytes.Buffer>fmt.Sprintf 靈活性:bytes.Buffer>fmt.Sprintf>=strings.Join>操作符+
正確使用,多次連線字串操作的情況下,bytes.Buffer應該是最快的。