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() {
// 将所有的 "<" 替换为 "<"、">" 替换为 ">"
r := strings.NewReplacer("<", "<", ">", ">")
fmt.Println(r.Replace("This is <b>HTML</b>!")) // This is <b>HTML</b>!
// 向标准输出中写入替换后的字符串
r.WriteString(os.Stdout, "This is <b>HTML</b>!") // This is <b>HTML</b>!
}
strings.Reader 类型
strings.Reader
类型通过从一个字符串读取数据,实现了io.Reader
、io.Seeker
、io.ReaderAt
、io.WriterTo
、io.ByteScanner
、io.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
}