Go 语言标准库之 strings 包

Go 语言的 strings 包实现了字符串的常用操作,本文介绍 strings 包的常用使用。

字符串比较 Compare/EqualFold

// 按照字典序比较两个字符串大小,a = b 返回 0,a < b 返回 -1,a > b 返回 1
// 不推荐使用这个函数,直接使用 =、>、< 比较会更加直观
func Compare(a, b string) int

// 判断两个字符串(不区分大小写)是否相同
func EqualFold(s, t string) bool

☕️ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    a, b := "gopher", "hello world"
    fmt.Println(strings.Compare(a, b)) // -1
    fmt.Println(strings.Compare(a, a)) // 0
    fmt.Println(strings.Compare(b, a)) // 1

    // 不区分大小写比较是否相同
    fmt.Println(strings.EqualFold("Go", "go")) // true
    fmt.Println(strings.EqualFold("壹", "一"))   // false
}

???? 查看Compare()函数的源码,发现它还是使用 =、>、< 进行字符串比较。源码如下:

func Compare(a, b string) int {
    if a == b {
        return 0
    }
    if a < b {
        return -1
    }
    return +1
}

是否有指定前/后缀 HasPrefix/HasSuffix

// 判断字符串 s 是否有前缀子串 prefix。如果 prefix 为 "",总是返回 true
func HasPrefix(s, prefix string) bool

// 判断字符串 s 是否有后缀子串 suffix。如果 suffix 为 "",总是返回 true
func HasSuffix(s, suffix string) bool

⭐️ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.HasPrefix("Gopher", "Go")) // true
    fmt.Println(strings.HasPrefix("Gopher", "C"))  // false
    fmt.Println(strings.HasPrefix("Gopher", ""))   // true

    fmt.Println(strings.HasSuffix("Amigo", "go"))  // true
    fmt.Println(strings.HasSuffix("Amigo", "Ami")) // false
    fmt.Println(strings.HasSuffix("Amigo", ""))    // true
}

是否存在指定子串/字符 Contains/ContainsRune/ContainsAny

// 判断字符串 s 是否包含子串 substr
func Contains(s, substr string) bool

// 判断字符串 s 是否包含字符 r
func ContainsRune(s string, r rune) bool

// 判断字符串 s 是否包含 chars 中的任一字符。如果 chars 为空串,直接返回 false
func ContainsAny(s, chars string) bool

✏️ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Contains("hello", "el")) // true
    fmt.Println(strings.Contains("hello", ""))   // true
    fmt.Println(strings.Contains("", ""))        // true

    fmt.Println(strings.ContainsRune("hello", 'e')) // true
    fmt.Println(strings.ContainsRune("hello", 'a')) // false

    fmt.Println(strings.ContainsAny("hello", "eo")) // true
    fmt.Println(strings.ContainsAny("hello", "ei")) // true
    fmt.Println(strings.ContainsAny("hello", ""))   // false
    fmt.Println(strings.ContainsAny("", ""))        // false
}

☕️ 查看Contains()/ContainsRune()/ContainsAny()三个函数的源码,发现它们只是调用了相应的index()函数,然后和 0 作比较返回 true 或 false。源码如下:

func Contains(s, substr string) bool {
    return Index(s, substr) >= 0
}

func ContainsRune(s string, r rune) bool {
    return IndexRune(s, r) >= 0
}

func ContainsAny(s, chars string) bool {
    return IndexAny(s, chars) >= 0
}

计算子串出现数目 Count

// 计算字符串 s 中不重叠的子串 sep 的数目。如果子串 sep 为空,直接返回 len(s) + 1
// 内部使用的 Rabin-Karp 算法进行字符串模式匹配
func Count(s, sep string) int

???? 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Count("aaa", "a"))  // 3
    fmt.Println(strings.Count("aaa", "aa")) // 1
    fmt.Println(strings.Count("aaa", ""))   // 4
}

定位索引 Index/IndexByte/IndexRune/IndexAny/IndexFunc

// 返回子串 sep 在字符串 s 中第一次出现的索引下标,不存在返回 -1
func Index(s, sep string) int

// 返回字节 c 在字符串 s 中第一次出现的索引下标,不存在返回 -1
func IndexByte(s string, c byte) int

// 返回字符 r 在字符串 s 中第一次出现的索引下标,不存在返回 -1
func IndexRune(s string, r rune) int

// 返回 chars 中任一字符在字符串 s 中第一次出现的索引下标,不存在或者 chars 为空串返回 -1
func IndexAny(s, chars string) int

// 返回字符串 s 中第一次满足函数 f 的索引下标(该处的字符 r 满足 f(r) == true),不存在返回 -1
func IndexFunc(s string, f func(rune) bool) int

✌ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Index("hello world", " wor")) // 5
    fmt.Println(strings.Index("hello world", "aaa"))  // -1

    fmt.Println(strings.IndexByte("hello world", 'l')) // 2
    fmt.Println(strings.IndexByte("hello world", 'x')) // -1

    fmt.Println(strings.IndexRune("hello world", 'l')) // 2
    fmt.Println(strings.IndexRune("hello world", 'x')) // -1

    fmt.Println(strings.IndexAny("hello world", "ie")) // 1
    fmt.Println(strings.IndexAny("hello world", "mc")) // -1

    f := func(c rune) bool {
        return c == 'w'
    }
    fmt.Println(strings.IndexFunc("hello world", f)) // 6
    fmt.Println(strings.IndexFunc("hello 世界", f))    // -1
}

定位索引 LastIndex/LastIndexAny/LastIndexByte/LastIndexFunc

// 返回子串 sep 在字符串 s 中最后一次出现的索引下标,不存在返回 -1
func LastIndex(s, sep string) int

// 返回字节 c 在字符串 s 中最后一次出现的索引下标,不存在返回 -1
func LastIndexByte(s string, c byte) int

// 返回 chars 中任一字符在字符串 s 中最后一次出现的索引下标,不存在或者 chars 为空串返回 -1
func IndexAny(s, chars string) int

// 返回字符串 s 中最后一次满足函数 f 的索引下标(该处的字符 r 满足 f(r) == true),不存在返回 -1
func IndexFunc(s string, f func(rune) bool) int

✍ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.LastIndex("hello world", " wor")) // 5
    fmt.Println(strings.LastIndex("hello world", "aaa"))  // -1

    fmt.Println(strings.LastIndexByte("hello world", 'l')) // 9
    fmt.Println(strings.LastIndexByte("hello world", 'x')) // -1

    fmt.Println(strings.LastIndexAny("hello world", "ie")) // 1
    fmt.Println(strings.LastIndexAny("hello world", "mc")) // -1

    f := func(c rune) bool {
        return c == 'l'
    }
    fmt.Println(strings.LastIndexFunc("hello world", f)) // 9
    fmt.Println(strings.LastIndexFunc("hello 世界", f))    // 3
}

大小写转换 ToLower/ToUpper

// 返回将字符串 s 的所有字母都转为对应的小写的新字符串
func ToLower(s string) string

// 返回将字符串 s 的所有字母都转为对应的大写的新字符串
func ToUpper(s string) string

???? 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.ToLower("HELLO WORLD")) // hello world
    fmt.Println(strings.ToUpper("hello world")) // HELLO WORLD
}

重复串联 Repeat

// 返回 count 个字符串 s 串联后的新字符串,count 不能传负数
func Repeat(s string, count int) string

☕️ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println("ba" + strings.Repeat("na", 2)) // banana
}

替换子串 repace/replaceAll

// 返回将字符串 s 中前 n 个不重叠子串 old 都替换为子串 new 的新字符串,如果 n < 0 会替换所有子串 old
func Replace(s, old, new string, n int) string

// 返回将字符串 s 中所有不重叠子串 old 都替换为子串 new 的新字符串,相当于使用 Replace 时n < 0
func ReplaceAll(s, old, new string) string

⭐️ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))      // oinky oinky oink
    fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) // moo moo moo
    fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "moo"))  // moo moo moo
}

字符映射替换 Map

// 返回对字符串 s 中每一个字符 r 执行 mapping(r) 操作后的新字符串
func Map(mapping func(rune) rune, s string) string

✏️ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    mapping := func(r rune) rune {
        if 'a' <= r && r <= 'z' {
            return r - 'a' + 'A'
        }
        return r
    }

    fmt.Println(strings.Map(mapping, "abcdef")) // ABCDEF
}

去除前后缀 Trim/TrimSpace/TrimFunc

// 返回将字符串 s 前后端所有 cutset 包含的字符都去除的新字符串
func Trim(s string, cutset string) string

// 返回将字符串 s 前后端所有空白字符(unicode.IsSpace 指定)都去除的新字符串
func TrimSpace(s string) string

// 返回将字符串 s 前后端字符 r(满足 f(r) = true)都去除的新字符串
func TrimFunc(s string, f func(rune) bool) string

???? 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Trim("?!?hello world!?!", "?!")) // hello world

    fmt.Println(strings.TrimSpace("   hello world   ")) // hello world

    f := func(r rune) bool {
        if r == '!' || r == '?' {
            return true
        }
        return false
    }
    fmt.Println(strings.TrimFunc("?!?hello world!?!", f)) // hello world
}

去除前缀 TrimLeft/TrimLeftFunc/TrimPrefix

// 返回将字符串 s 前端所有 cutset 包含的字符都去除的新字符串
func TrimLeft(s string, cutset string) string

// 返回将字符串 s 前端字符 r(满足 f(r) = true)都去除的新字符串
func TrimLeftFunc(s string, f func(rune) bool) string

// 返回将字符串 s 可能的前缀子串 prefix 去除的新字符串
func TrimPrefix(s, prefix string) string

✌ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.TrimLeft("?!?hello world!?!", "?!")) // hello world!?!

    f := func(r rune) bool {
        if r == '!' || r == '?' {
            return true
        }
        return false
    }
    fmt.Println(strings.TrimLeftFunc("?!?hello world!?!", f)) // hello world!?!

    fmt.Println(strings.TrimPrefix("?!?hello world!?!", "?!?hell")) // o world!?!
}

去除后缀 TrimRight/TrimRightFunc/TrimSuffix

// 返回将字符串 s 后端所有 cutset 包含的字符都去除的新字符串
func TrimRight(s string, cutset string) string

// 返回将字符串 s 后端字符 r(满足 f(r) = true)都去除的新字符串
func TrimRightFunc(s string, f func(rune) bool) string

// 返回将字符串 s 可能的后缀子串 suffix 去除的新字符串
func TrimSuffix(s, suffix string) string

✍ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.TrimRight("?!?hello world!?!", "?!")) // ?!?hello world

    f := func(r rune) bool {
        if r == '!' || r == '?' {
            return true
        }
        return false
    }
    fmt.Println(strings.TrimRightFunc("?!?hello world!?!", f)) // ?!?hello world

    fmt.Println(strings.TrimSuffix("?!?hello world!?!", "orld!?!")) // ?!?hello w
}

分割字符串 Fields/FieldsFunc

// 返回将字符串按照空白(unicode.IsSpace确定,可以是一到多个连续的空白字符)分割的多个字符串
// 如果字符串全部是空白或者是空字符串的话,会返回空切片
func Fields(s string) []string

// 返回将字符串按照分隔符 r(满足 f(r) == true)分割的多个字符串
// 如果字符串全部是分隔符或者是空字符串的话,会返回空切片
func FieldsFunc(s string, f func(rune) bool) []string

???? 示例代码

package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    fmt.Printf("%q\n", strings.Fields("  hello world go ")) // ["hello" "world" "go"]

    f := func(r rune) bool {
        return !unicode.IsLetter(r) && !unicode.IsNumber(r)
    }
    fmt.Printf("%q\n", strings.FieldsFunc(" hello  world go   ", f)) // ["hello" "world" "go"]
}

分割字符串 Split/SplitN/SplitAfter/SplitAfterN

// 用去除每一个 sep 的方式对字符串 s 进行分割,会分割到结尾,返回分割出的所有子串组成的切片
// 每一个 sep 都会进行一次分割,即使两个 sep 相邻,也会进行两次分割
// 如果 sep 空串,Split 会将字符串 s 分割为一个字符一个子串
func Split(s, sep string) []string

// 类似 Split,但是参数 n 决定分割后的切片大小。n < 0:等同 Split(s, sep);n == 0:返回空切片;
// n > 0:最多分割出 n 个子串,最后一个子串包含未进行切割的部分
func SplitN(s, sep string, n int) []string

// 用在每一个 sep 后面切割的方式对字符串 s 进行分割,会分割到结尾,返回分割出的所有子串组成的切片
// 每一个 sep 都会进行一次分割,即使两个 sep 相邻,也会进行两次分割
// 如果 sep 空串,Split 会将字符串 s 分割为一个字符一个子串
func SplitAfter(s, sep string) []string

// 类似 SplitAfter,但是参数 n 决定分割后的切片大小。n < 0:等同 SplitAfter(s, sep);
// n == 0:返回空切片;n > 0:最多分割出 n 个子串,最后一个子串包含未进行切割的部分
func SplitAfterN(s, sep string, n int) []string

☕️ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    // 通过去除每一个 sep 的方式进行分割,分割出来的子串后面不带 sep
    fmt.Printf("%q\n", strings.Split(",foo,bar,", ",")) // ["" "foo" "bar" ""]

    // SplitN 指定了返回的切片的长度,切片最后一部分是未被处理的
    fmt.Printf("%q\n", strings.SplitN(",foo,bar,", ",", 2))  // ["" "foo,bar,"]
    fmt.Printf("%q\n", strings.SplitN(",foo,bar,", ",", 0))  // []
    fmt.Printf("%q\n", strings.SplitN(",foo,bar,", ",", -1)) // ["" "foo" "bar" ""]

    // 通过在每一个 sep 后面进行切割的方式进行分割,分割出来的子串后面保留 sep
    fmt.Printf("%q\n", strings.SplitAfter(",foo,bar,", ",")) // ["," "foo," "bar," ""]

    fmt.Printf("%q\n", strings.SplitAfterN(",foo,bar,", ",", 2))  // ["," "foo,bar,"]
    fmt.Printf("%q\n", strings.SplitAfterN(",foo,bar,", ",", 0))  // []
    fmt.Printf("%q\n", strings.SplitAfterN(",foo,bar,", ",", -1)) // ["," "foo," "bar," ""]
}

连接字符串 Join

// 将一系列字符串连接为一个新的字符串,之间用 sep 来分隔
func Join(elems []string, sep string) string

⭐ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    ss := []string{"abc", "def", "gh"}
    fmt.Println(strings.Join(ss, ",")) // abc,def,gh
}

strings.Replacer 类型

strings.Replacer类型用于进行一系列字符串的替换,实例化通过func NewReplacer(oldnew ...string) *Replacer函数进行,其中不定参数 oldnew 是old-new对,即进行多个替换。如果 oldnew 长度与奇数,会导致 panic。

type Replacer struct {
    // 内含隐藏或非导出字段
}

// 使用提供的多组 old、new 字符串对创建并返回一个 *Replacer。替换是依次进行的,匹配时不会重叠
func NewReplacer(oldnew ...string) *Replacer

// 返回 s 的所有替换进行完后的拷贝
func (r *Replacer) Replace(s string) string

// 向 w 中写入 s 的所有替换进行完后的拷贝
func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)

✏️ 示例代码

package main

import (
    "fmt"
    "os"
    "strings"
)

func main() {
    // 将所有的 "<" 替换为 "&lt;"、">" 替换为 "&gt;"
    r := strings.NewReplacer("<", "&lt;", ">", "&gt;")
    fmt.Println(r.Replace("This is <b>HTML</b>!")) // This is &lt;b&gt;HTML&lt;/b&gt;!

    // 向标准输出中写入替换后的字符串
    r.WriteString(os.Stdout, "This is <b>HTML</b>!") // This is &lt;b&gt;HTML&lt;/b&gt;!
}

strings.Reader 类型

strings.Reader类型通过从一个字符串读取数据,实现了io.Readerio.Seekerio.ReaderAtio.WriterToio.ByteScannerio.RuneScanner接口。

type Reader struct {
    s        string // 要读取的字符串
    i        int64  // 当前读取的偏移量位置,从 i 处开始读取数据
    prevRune int    // 读取的前一个字符的偏移量位置,小于 0 表示之前未读取字符
}

// 创建一个从 s 读取数据的 Reader。本函数类似 bytes.NewBufferString,但是更有效率,且为只读的
func NewReader(s string) *Reader

// 返回 r 包含的字符串还没有被读取的部分的长度
func (r *Reader) Len() int

// 从 r 中读取最多 len(b) 字节数据并写入 b,返回读取的字节数和可能遇到的任何错误。如果无可读数据返回 0 个字节,且返回值 err 为 io.EOF
func (r *Reader) Read(b []byte) (n int, err error)

// 将索引位置 off 之后的所有数据写入到 b 中,返回读取的字节数和读取过程中遇到的错误
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)

// 读取并返回一个字节。如果没有可用的数据,会返回错误
func (r *Reader) ReadByte() (b byte, err error)

// 撤消前一次的 ReadByte 操作,即偏移量向前移动一个字节,UnreadByte 操作前必须要有 ReadByte 操作 
func (r *Reader) UnreadByte() error

// 读取并返回一个字符写入到 ch 中,size 为 ch 的字节大小,err 返回读取过程遇到的错误
func (r *Reader) ReadRune() (ch rune, size int, err error)

// 撤销前一次的 ReadRune 操作,即偏移量向前移动一个字符,UnreadRune 操作前必须要有 ReadRune 操作
func (r *Reader) UnreadRune() error

// 设置下一次读的位置。offset 为相对偏移量,而 whence 决定相对位置:0 为相对文件开头,1 为相对当前位置,2 为相对文件结尾
// 返回新的偏移量(相对开头)和可能的错误
func (r *Reader) Seek(offset int64, whence int) (int64, error)

// 将底层数据切换为 s,同时复位所有标记(读取位置等信息)
func (r *Reader) Reset(s string)

// 从 r 中读取数据写入接口 w 中,返回读取的字节数和可能遇到的任何错误
func (r *Reader) WriteTo(w io.Writer) (n int64, err error)

???? 示例代码

package main

import (
    "fmt"
    "log"
    "os"
    "strings"
)

func main() {
    s := "hello world!!!"
    r := strings.NewReader(s)

    // 返回未读的数据长度
    fmt.Println(r.Len()) // 14

    // 读取三个字节到字节切片
    byteSlice := make([]byte, 3)
    numBytesRead, err := r.Read(byteSlice)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Read %d bytes: %s\n", numBytesRead, byteSlice) // Read 3 bytes: hel

    // 读取一个字节, 如果读取不成功会返回 Error
    myByte, err := r.ReadByte()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Read 1 byte: %c\n", myByte) // Read 1 byte: l

    // 撤消前一次的 ReadByte 操作,偏移量会向前移动一个字节
    err = r.UnreadByte()
    if err != nil {
        log.Fatal(err)
    }

    // 返回未读的数据长度
    fmt.Println(r.Len()) // 11

    // 将剩下未读的数据写入标准输出中
    num, err := r.WriteTo(os.Stdout) // lo world!!!
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Read %d bytes\n", num) // Read 11 bytes
}

strings.Builder 类型

在字符串拼接时可以通过strings.Builder的写入方法来高效构建字符串,它最小化了内存拷贝。

方法介绍

type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}

// 预分配内存
func (b *Builder) Grow(n int)

// 返回当前 b 底层用于存储数据的 []byte 切片的长度和容量
func (b *Builder) Len() int
func (b *Builder) Cap() int 

// 将当前 b 清空
func (b *Builder) Reset() 

// 往当前 b 中写入不同类型的数据,返回写入数据的字节大小和发生的错误
func (b *Builder) Write(p []byte) (int, error)  
func (b *Builder) WriteByte(c byte) error 
func (b *Builder) WriteRune(r rune) (int, error) 
func (b *Builder) WriteString(s string) (int, error) 

// 将当前 b 中存储的数据转换为字符串输出
func (b *Builder) String() string 

✌ 示例代码

package main

import (
    "fmt"
    "strings"
)

func main() {
    var b strings.Builder
    // 四种写入方法
    b.Write([]byte("hello"))
    b.WriteByte(' ')
    b.WriteRune('您')
    b.WriteString("好")

    for i := 1; i <= 3; i++ {
        // strings.Builder 实现了 io.Writer 接口
        fmt.Fprintf(&b, "%d...", i)
    }
    fmt.Println(b.String()) // hello 您好1...2...3...
    fmt.Println(b.Len())    // 24
    fmt.Println(b.Cap())    // 48
}

底层分析

☕️ 存储结构

strings.Builder底层是通过内部的[]byte来存储数据:

type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}

当调用写入方法的时候,数据实际上是被追加(append)到[]byte上:

func (b *Builder) Write(p []byte) (int, error) {
    b.copyCheck()
    b.buf = append(b.buf, p...)
    return len(p), nil
}

由于底层是 Slice,所以写入时可能会导致 Slice 扩容,所以strings.Builder提供了Grow()方法预分配内存,避免多次扩容。Grow()方法的具体实现如下:

func (b *Builder) grow(n int) {
    buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
    copy(buf, b.buf)
    b.buf = buf
}

func (b *Builder) Grow(n int) {
    b.copyCheck()
    if n < 0 {
        panic("strings.Builder.Grow: negative count")
    }
    if cap(b.buf)-len(b.buf) < n {
        b.grow(n)
    }
}

Grow()方法保证了其内部的 Slice 一定能够写入 n 个字节,只有当 Slice 剩余空间不足以写入 n 个字节时,扩容才会发生。

⭐️ 不允许被拷贝

strings.Builder不允许被拷贝,当试图拷贝strings.Builder并写入的时候,程序会报错:

package main

import (
    "fmt"
    "strings"
)

func main() {
    var b1 strings.Builder
    b1.WriteString("aaa")
    b2 := b1
    b2.WriteString("bbb")
    fmt.Println(b2.String())
}

// panic: strings: illegal use of non-zero Builder copied by value

strings.Builder结构体有一个指向*Builder的指针 add,在调用b1.WriteString()方法之后,b1 内部的指针会指向自己:

func (b *Builder) Write(p []byte) (int, error) {
    b.copyCheck()
    //...
}

func (b *Builder) copyCheck() {
    if b.addr == nil {
        // This hack works around a failing of Go's escape analysis
        // that was causing b to escape and be heap allocated.
        // See issue 23382.
        // TODO: once issue 7921 is fixed, this should be reverted to
        // just "b.addr = b".
        b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
    } else if b.addr != b {
        panic("strings: illegal use of non-zero Builder copied by value")
    }
}

而我们执行b1 = b2时,结构体内部同样拷贝了指向 b1 的指针 add,也就是说b2.add = &b1。所以,当对 b2 进行b2.WriteString()操作时,会再次进入copyCheck()方法,直接报 panic 错误。

对于一个空strings.Builder的拷贝是允许的,因为此时 add 指针为 nil,执行copyCheck()时不会进行b.add != b的判断条件中。

package main

import (
    "fmt"
    "strings"
)

func main() {
    var b1 strings.Builder
    fmt.Println(b1) // {<nil> []}
    b2 := b1
    b2.WriteString("aaa")
    fmt.Println(b2) // {0xc0000cdf30 [97 97 97]}
}

✏️ String()

strings.Builder返回当前数据的字符串时,为了节省内存分配,它通过使用指针技术将内部的[]byte 转换为字符串,所以String()方法在转换的时候节省了时间和空间。其具体实现方式如下:

func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

???? 不支持并发

strings.Builder不支持并发读写,是不安全的,所以最好在单协程中使用。如果strings.Builder支持并发,下面代码运行结果应该是 1000:

package main

import (
    "fmt"
    "strings"
    "sync"
)

func main() {
    var b strings.Builder
    var wait sync.WaitGroup
    for n := 0; n < 10000; {
        wait.Add(1)
        go func() {
            b.WriteString("1")
            n++
            wait.Done()
        }()
    }
    wait.Wait()
    fmt.Println(b.Len()) // 9349
}

参考

  1. Go中strings的常用方法详解
  2. Golang 中 strings.builder 的 7 个要点
  3. strings.Builder 源码分析
  4. go语言中strings包的用法汇总