Golang理解-字符串拼接的幾種方式


Golang中的字符串

Golang 中的string類型存儲的字符串是不可變的, 如果要修改string內容需要將string轉換為[]byte或[]rune,並且修改后的string內容是重新分配的, 原字符串將被gc回收;

package main

import (
		"fmt"
)
func main() {
    s := "hi, go"
    fmt.Printf("value of str: %v\n", s)
    fmt.Printf("ptr of str: %p\n", &s)
    // 修改, 將,修改為!
    bs := []byte(str)
    bs[2] = '!' 
    fmt.Printf("value of lstr: %v\n", string(bs))
    fmt.Printf("ptr of lstr: %p\n", &bs)
}

結果:

value of str: hi, go
ptr of str: 0xc00000e1f0
value of lstr: hi! go
ptr of lstr: 0xc00000a080

可以看到bs 和 s 的地址空間不同了,可見字符串的修改是會重新分配的;

Golang中string有2種類型, 只包含ASCII碼的string, 已經包含中文等其他復雜類型的string; 我們知道中文是占3個字節的;

其中:只包含ASCII碼的string的string 能通過索引的方式查找對應位置的字符;而包含中文的string類型rune,要想完整的顯示中文,需要使用for…range循環;

Golang字符串拼接方法

golang中要實現字符串的拼接,有很多種方法,最常見的當然是使用運算符"+"進行拼接了,還有很多其他的方法,下面依次介紹,並說明其優缺點.

直接使用運算符

// 不換行
str := "hello, " + "golang"

// 換行,換行是"+" 必須在上一行的結尾處
str1 := "The only person " +
				"standing in your way " +
				"is you"

上面提到golang里面的字符串都是不可變的,每次運算都會產生一個新的字符串.

所以使用運算符"+"連接字符串會產生很多臨時的無用的字符串,會給 gc 帶來額外的負擔,所以性能比較差.


使用fmt.Sprintf

fmt包是golang中的基礎包,fmt 包實現了格式化I/O函數,類似於C的 printf 和 scanf.

在fmt包中提供了一個方法func Sprintf(format string, a ...interface{}) string,使用格式話的方式可以將多個字符串拼接到一起

str := fmt.Sprintf("%s %s %s", "format", "string", "by fmt.Sprintf")

這種方式,使用簡單,雖然不會像"+"連接那樣生成多余的string,但是內部實現頗為復雜,性能不是很好.

使用strings.Join

golang的 strings 包為字符串的拼接提供了一個方法func Join(a []string, sep string) string, Join的內部實現比fmt.Sprintf要簡單的多,思路就是: Join會先根據字符串數組的內容,計算出一個拼接之后的長度,然后申請對應大小的內存,一個一個字符串填入.代碼如下:

// Join 將傳如的字符串連接成一個字符串
func Join(a []string, sep string) string {
  	// 如果字符串數量少,直接使用運算符拼接
    switch len(a) {
    case 0:
      return ""
    case 1:
      return a[0] 
    case 2:
      return a[0] + sep + a[1]
    case 3:
      return a[0] + sep + a[1] + sep + a[2]
    }

    // 計算最終字符串的字符大小
    // 首先計算連接符sep的大小
    n := len(sep) * (len(a) -1)

    // 計算被連接的字符串的字符數
    for _, value := range a {
      n += len(value)
    }

    // 知道了總的字符數量,創建對應大小的數組
    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

bytes包中的Buffer提供了一個方法 func (b *Buffer) WriteString(s string) (n int, err error)

WriteString將s的內容追加到緩沖區,並根據需要增加緩沖區。返回值n為s的長度;err總是nil。

如果緩沖區太大,WriteString將會因為ErrTooLarge而陷入恐慌。

package main

import (
    "fmt"
    "bytes"
)

func main() {
    // 聲明一個Buffer
    var buf bytes.Buffer
    buf.WriteString("good ")
    buf.WriteString("boy!")
    fmt.Println(buf.String()) // good boy!
}

這個比較理想,可以當成可變字符使用,對內存的增長也有優化,如果能預估字符串的長度,還可以用 buffer.Grow() 接口來設置 capacity。

使用strings.Builder

strings.Builder 內部通過 slice 來保存和管理內容。slice 內部則是通過一個指針指向實際保存內容的數組。

strings.Builder 同樣也提供了 Grow() 來支持預定義容量。

當我們可以預定義我們需要使用的容量時,strings.Builder 就能避免擴容而創建新的 slice 了。strings.Builder是非線程安全,性能上和 bytes.Buffer 相差無幾。

package main

import (
    "fmt"
    "strings"
)

func main() {
    // 聲明一個Buffer
    var buf strings.Builder
    buf.WriteString("good ")
    buf.WriteString("boy!")
    fmt.Println(buf.String()) // good boy!
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM