zoukankan      html  css  js  c++  java
  • 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包的用法汇总
  • 相关阅读:
    盛京剑客系列21:再强调一遍:机会在MSCI成份,别走偏了
    盛京剑客系列20:平仓中兴通讯,获利45.51%,继续加仓优质个股
    盛京剑客系列19:推书《战胜华尔街》
    盛京剑客系列18:很多人因为恐惧脚下的小土坑,却丢掉了一米远处的大金矿
    盛京剑客系列17:市场暴跌下投资组合的调整
    盛京剑客系列16:推书《股市稳赚》
    盛京剑客系列15:割韭秘籍
    盛京剑客系列14:对高估值医药股要谨慎
    盛京剑客系列13:披露指数的密码,曙光就在前方
    leetcode -- Longest Valid Parentheses
  • 原文地址:https://www.cnblogs.com/zongmin/p/15625745.html
Copyright © 2011-2022 走看看