1. 程式人生 > >go字串拼接操作

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應該是最快的。