欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang中拼接字符串的6種方式性能對比

 更新時間:2025年03月14日 15:26:35   作者:想喝奶茶_  
golang的string類型是不可修改的,對于拼接字符串來說,本質上還是創(chuàng)建一個新的對象將數(shù)據(jù)放進去,主要有6種拼接方式,下面小編就來為大家詳細講講吧

golang的string類型是不可修改的,對于拼接字符串來說,本質上還是創(chuàng)建一個新的對象將數(shù)據(jù)放進去。主要有以下幾種拼接方式

拼接方式介紹

1.使用string自帶的運算符+

ans = ans + s

2. 使用格式化輸出fmt.Sprintf

ans = fmt.Sprintf("%s%s", ans, s)

3. 使用strings的join函數(shù)

一般適用于將字符串數(shù)組轉化為特定間隔符的字符串的情況

ans=strings.join(strs,",")

4. 使用strings.Builder

builder := strings.Builder{}
builder.WriteString(s)
return builder.String()

5. 使用bytes.Buffer

buffer := new(bytes.Buffer)
buffer.WriteString(s)
return buffer.String()

6. 使用[]byte,并且提前設置容量

ans := make([]byte, 0, len(s)*n)
ans = append(ans, s...)

性能對比

先寫一個隨機生成長度為n的字符串的函數(shù)

func getRandomString(n int) string {
    var tmp = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    ans := make([]uint8, 0, n)
    for i := 0; i < n; i++ {
        ans = append(ans, tmp[rand.Intn(len(tmp))])
    }
    return string(ans)
}

接下來分別寫出上述拼接方式的實現(xiàn),假設每次都拼接n次字符串s后返回。

1.使用string自帶的運算符+

循環(huán)n次,每次都令答案字符串ans+源字符串s

func plusOperatorJoin(n int, s string) string {
    var ans string
    for i := 0; i < n; i++ {
        ans = ans + s
    }
    return ans
}

2. 使用格式化輸出fmt.Sprintf

循環(huán)n次,使用fmt.Sprintf達到拼接的目的

func sprintfJoin(n int, s string) string {
    var ans string
    for i := 0; i < n; i++ {
        ans = fmt.Sprintf("%s%s", ans, s)
    }
    return ans
}

3. 使用strings的join函數(shù)

拼接同一個字符串的話不適合用join函數(shù),所以跳過這種方式

4. 使用strings.Builder

初始化strings.Builder,循環(huán)n次,每次調用WriteString方法

func stringBuilderJoin(n int, s string) string {
    builder := strings.Builder{}
    for i := 0; i < n; i++ {
        builder.WriteString(s)
    }
    return builder.String()
}

5. 使用bytes.Buffer

初始化bytes.Buffer,循環(huán)n次,每次調用WriteString方法

func bytesBufferJoin(n int, s string) string {
    buffer := new(bytes.Buffer)
    for i := 0; i < n; i++ {
        buffer.WriteString(s)
    }
    return buffer.String()
}

6. 使用[]byte,并且提前設置容量

定義ans為byte數(shù)組,并提前設置容量為len(s)∗n

func bytesJoin(n int, s string) string {
    ans := make([]byte, 0, len(s)*n)
    for i := 0; i < n; i++ {
        ans = append(ans, s...)
    }
    return string(ans)
}

測試代碼

先隨機生成一個長度為10的字符串,然后拼接10000次。

package high_strings

import "testing"

func benchmark(b *testing.B, f func(int, string) string) {
	var str = getRandomString(10)
	for i := 0; i < b.N; i++ {
		f(10000, str)
	}
}

func BenchmarkPlusOperatorJoin(b *testing.B) {
	benchmark(b, plusOperatorJoin)
}
func BenchmarkSprintfJoin(b *testing.B) {
	benchmark(b, sprintfJoin)
}
func BenchmarkStringBuilderJoin(b *testing.B) {
	benchmark(b, stringBuilderJoin)
}
func BenchmarkBytesBufferJoin(b *testing.B) {
	benchmark(b, bytesBufferJoin)
}
func BenchmarkBytesJoin(b *testing.B) {
	benchmark(b, bytesJoin)
}

測試結果

使用[]byte>strings.Builder≥bytes.Buffer>ffmt.Sprintf > +運算符

源碼分析

1.使用string自帶的運算符+

代碼在runtime\string.go里

// 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 {
            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)
    for _, x := range a {
        copy(b, x)
        b = b[len(x):]
    }
    return s
}

首先計算拼接后的字符串長度

如果只有一個字符串并且不在棧上就直接返回

如果buf不為空并且buf可以放下這些字符串,就把拼接后的字符串放在buf里,否則在堆上重新申請一塊內存

func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) {
    if buf != nil && l <= len(buf) {
        b = buf[:l]
        s = slicebytetostringtmp(&b[0], len(b))
    } else {
        s, b = rawstring(l)
    }
    return
}
// rawstring allocates storage for a new string. The returned
// string and byte slice both refer to the same storage.
// The storage is not zeroed. Callers should use
// b to set the string contents and then drop b.
func rawstring(size int) (s string, b []byte) {
    p := mallocgc(uintptr(size), nil, false)
    return unsafe.String((*byte)(p), size), unsafe.Slice((*byte)(p), size)
}

然后遍歷數(shù)組,將字符串copy過去

2. 使用strings.Builder

介紹:strings.Builder用于使用Write方法高效地生成字符串,它最大限度地減少了內存復制

拼接過程:builder里有一個byte類型的切片,每次調用WriteString的時候,是直接往該切片里追加字符串。因為切片底層的擴容機制是以倍數(shù)申請的,所以對比1而言,2的內存消耗要更少。

**結果返回:**在返回字符串的String方法里,是將buf數(shù)組轉化為字符串直接返回的。

擴容機制: 想要緩沖區(qū)容量增加n個字節(jié),擴容后容量變?yōu)?∗len+n

// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
	addr *Builder // of receiver, to detect copies by value
	buf  []byte
}

// String returns the accumulated string.
func (b *Builder) String() string {
	return unsafe.String(unsafe.SliceData(b.buf), len(b.buf))
}

// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
func (b *Builder) grow(n int) {
	buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
	copy(buf, b.buf)
	b.buf = buf
}
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
	b.copyCheck()
	b.buf = append(b.buf, s...)
	return len(s), nil
}

3. 使用bytes.Buffer

介紹bytes.Buffer跟strings.Builder的底層都是byte數(shù)組,區(qū)別在于擴容機制和返回字符串的String方法。

結果返回: 因為bytes.Buffer實際上是一個流式的字節(jié)緩沖區(qū),可以向尾部寫入數(shù)據(jù),也可以讀取頭部的數(shù)據(jù)。所以在返回字符串的String方法里,只返回了緩沖區(qū)里未讀的部分,所以需要重新申請內存來存放返回的結果。內存會比strings.Builder稍慢一些。

擴容機制: 想要緩沖區(qū)容量至少增加n個字節(jié),m是未讀的長度,c是當前的容量。

優(yōu)化點在于如果n<=c/2−m,也就是當前容量的一半都大于等于現(xiàn)有的內容(未讀的字節(jié)數(shù))加上所需要增加的字節(jié)數(shù),就復用當前的數(shù)組,把未讀的內容拷貝到頭部去。

We can slide things down instead of allocating a new slice. We only need m+n <= c to slide, but we instead let capacity get twice as large so we don’t spend all our time copying.

我們可以向下滑動,而不是分配一個新的切片。我們只需要m+n<=c來滑動,但我們讓容量增加了一倍,這樣我們就不會把所有的時間都花在復制上。

否則的話也是2∗len+n的擴張

// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
    buf      []byte // contents are the bytes buf[off : len(buf)]
    off      int    // read at &buf[off], write at &buf[len(buf)]
    lastRead readOp // last read operation, so that Unread* can work correctly.
}
// String returns the contents of the unread portion of the buffer
// as a string. If the Buffer is a nil pointer, it returns "<nil>".
//
// To build strings more efficiently, see the strings.Builder type.
func (b *Buffer) String() string {
    if b == nil {
        // Special case, useful in debugging.
        return "<nil>"
    }
    return string(b.buf[b.off:])
}
// WriteString appends the contents of s to the buffer, growing the buffer as
// needed. The return value n is the length of s; err is always nil. If the
// buffer becomes too large, WriteString will panic with ErrTooLarge.
func (b *Buffer) WriteString(s string) (n int, err error) {
    b.lastRead = opInvalid
    m, ok := b.tryGrowByReslice(len(s))
    if !ok {
        m = b.grow(len(s))
    }
    return copy(b.buf[m:], s), nil
}

???????// grow grows the buffer to guarantee space for n more bytes.
// It returns the index where bytes should be written.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) grow(n int) int {
    m := b.Len()
    // If buffer is empty, reset to recover space.
    if m == 0 && b.off != 0 {
        b.Reset()
    }
    // Try to grow by means of a reslice.
    if i, ok := b.tryGrowByReslice(n); ok {
        return i
    }
    if b.buf == nil && n <= smallBufferSize {
        b.buf = make([]byte, n, smallBufferSize)
        return 0
    }
    c := cap(b.buf)
    if n <= c/2-m {
        // We can slide things down instead of allocating a new
        // slice. We only need m+n <= c to slide, but
        // we instead let capacity get twice as large so we
        // don't spend all our time copying.
        copy(b.buf, b.buf[b.off:])
    } else if c > maxInt-c-n {
        panic(ErrTooLarge)
    } else {
        // Add b.off to account for b.buf[:b.off] being sliced off the front.
        b.buf = growSlice(b.buf[b.off:], b.off+n)
    }
    // Restore b.off and len(b.buf).
    b.off = 0
    b.buf = b.buf[:m+n]
    return m
}

參考:GoLang bytes.Buffer基礎使用方法詳解

到此這篇關于Golang中拼接字符串的6種方式性能對比的文章就介紹到這了,更多相關Go拼接字符串內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Go 語言中程序編譯過程詳解

    Go 語言中程序編譯過程詳解

    本文旨在深入探討Go語言的編譯機制和最新的模塊管理系統(tǒng)——Go Modules,通過詳細的示例和步驟,我們將演示從簡單的 “Hello World” 程序到使用第三方庫的更復雜項目的開發(fā)過程,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • golang版本升級的簡單實現(xiàn)步驟

    golang版本升級的簡單實現(xiàn)步驟

    個人感覺Go在眾多高級語言中,是在各方面都比較高效的,下面這篇文章主要給大家介紹了關于golang版本升級的簡單實現(xiàn)步驟,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-02-02
  • Go中的Context實現(xiàn)原理以及正確使用方式

    Go中的Context實現(xiàn)原理以及正確使用方式

    在 Go 語言中,Context 包是一種非常常用的工具,它被用來管理 goroutine 之間的通信和取消,本文將深入探討Context 包的基本原理,包括使用場景、原理和一些最佳實踐,感興趣的小伙伴跟著小編一起來看看吧
    2024-11-11
  • 細細探究Go 泛型generic設計

    細細探究Go 泛型generic設計

    這篇文章主要帶大家細細探究了Go 泛型generic設計及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • Go實現(xiàn)mongodb增刪改查工具類的代碼示例

    Go實現(xiàn)mongodb增刪改查工具類的代碼示例

    這篇文章主要給大家介紹了關于Go實現(xiàn)mongodb增刪改查工具類的相關資料,MongoDB是一個NoSQL數(shù)據(jù)庫,它提供了靈活的文檔存儲模型以及強大的查詢和操作功能,需要的朋友可以參考下
    2023-10-10
  • 自己動手用Golang實現(xiàn)約瑟夫環(huán)算法的示例

    自己動手用Golang實現(xiàn)約瑟夫環(huán)算法的示例

    這篇文章主要介紹了自己動手用Golang實現(xiàn)約瑟夫環(huán)算法的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-12-12
  • Golang測試func?TestXX(t?*testing.T)的使用詳解

    Golang測試func?TestXX(t?*testing.T)的使用詳解

    一般Golang中的測試代碼都以xxx_test.go的樣式,在命名測試函數(shù)的時候以Testxx開頭,下面給大家介紹Golang測試func?TestXX(t?*testing.T)的使用,感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • 一文了解Go語言中的函數(shù)與方法的用法

    一文了解Go語言中的函數(shù)與方法的用法

    與大部分語言一致,Go語言中的函數(shù)與方法定義與其他語言基本一致,但也有一定的差別。本文將通過示例詳細講講Go語言中函數(shù)與方法的用法,感興趣的可以學習一下
    2022-07-07
  • go語言實現(xiàn)字符串與其它類型轉換(strconv包)

    go語言實現(xiàn)字符串與其它類型轉換(strconv包)

    strconv包是Go語言標準庫的一部分,主要提供字符串與基本數(shù)據(jù)類型之間的轉換功能,使用strconv包可以方便地在不同類型之間進行轉換,滿足日常編程中的需求,感興趣的可以了解一下
    2024-10-10
  • GO將mysql?中?decimal?數(shù)據(jù)類型映射到?protobuf的操作方法

    GO將mysql?中?decimal?數(shù)據(jù)類型映射到?protobuf的操作方法

    這篇文章主要介紹了go如何優(yōu)雅地將?mysql?中?decimal?數(shù)據(jù)類型映射到?protobuf,本文主要展示一下在 protobuf中 float與double的一個區(qū)別,結合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-09-09

最新評論