目录
  • 1、string类型
  • 2、strings包
    • 2.1 strings.builder类型
    • 2.2 strings.reader类型
  • 3、bytes.buffer
    • 3.1 bytes.buffer:写数据
    • 3.2 bytes.buffer:读数据
  • 4、字符串拼接
    • 4.1 直接相加
    • 4.2strings.builder
    • 4.3 strings.join()
    • 4.4 bytes.buffer
    • 4.5 append方法
    • 4.6 fmt.sprintf
  • 5、字符串拼接性能测试

    1、string类型

    string类型的值可以拆分为一个包含多个字符(rune类型)的序列,也可以被拆分为一个包含多个字节 (byte类型) 的序列。其中一个rune类型值代表一个unicode 字符,一个rune类型值占用四个字节,底层就是一个 utf-8 编码值,它其实是int32类型的一个别名类型。

    package main
    
    import (
     "fmt"
    )
    
    func main() {
     str := "你好world"
     fmt.printf("the string: %q\n", str)
     fmt.printf("runes(char): %q\n", []rune(str))
     fmt.printf("runes(hex): %x\n", []rune(str))
     fmt.printf("bytes(hex): [% x]\n", []byte(str))
    }
    

    执行结果:

    the string: “你好world”
    runes(char): [‘你’ ‘好’ ‘w’ ‘o’ ‘r’ ‘l’ ‘d’]
    runes(hex): [4f60 597d 77 6f 72 6c 64]
    bytes(hex): e4 bd a0 e5 a5 bd 77 6f 72 6c 64

    可以看到,英文字符使用一个字节,而中文字符需要三个字节。下面使用 for range 语句对上面的字符串进行遍历:

    for index, value := range str {
        fmt.printf("%d: %q [% x]\n", index, value, []byte(string(value)))
    }
    
    

    执行结果如下:

    0: ‘你’ [e4 bd a0]
    3: ‘好’ [e5 a5 bd]
    6: ‘w’ [77]
    7: ‘o’ [6f]
    8: ‘r’ [72]
    9: ‘l’ [6c]
    10: ‘d’ [64]

    index索引值不是0-6,相邻unicode 字符的索引值不一定是连续的,因为中文字符占用了3个字节,宽度为3。

    2、strings包

    2.1 strings.builder类型

    strings.builder的优势主要体现在字符串拼接上,相比使用+拼接,效率更高。

    • strings.builder已存在的值不可改变,只能重置(reset()方法)或者拼接更多的内容。
    • 一旦调用了builder值,就不能再以任何方式对其进行复制,比如函数间值传递、通道传递值、把值赋予变量等。
    • 在进行拼接时,builder值会自动地对自身的内容容器进行扩容,也可以使用grow方法进行手动扩容。
    package main
    
    import (
     "fmt"
     "strings"
    )
    func main() {
     var builder1 strings.builder
     builder1.writestring("hello")
     builder1.writebyte(' ')
     builder1.writestring("world")
     builder1.write([]byte{' ', '!'})
    
     fmt.println(builder1.string()) 
    
     f1 := func(b strings.builder) {
      // b.writestring("world !")  //会报错
     }
     f1(builder1)
    
     builder1.reset()
     fmt.printf("the length 0f builder1: %d\n", builder1.len())
    
    }
    
    

    执行结果:

    hello world !
    the length 0f builder1: 0

    2.2 strings.reader类型

    strings.reader类型可以用于高效地读取字符串,它通过使用已读计数机制来实现了高效读取,已读计数保存了已读取的字节数,也代表了下一次读取的起始索引位置。

    package main
    
    import (
     "fmt"
     "strings"
    )
    func main() { 
     reader1 := strings.newreader("hello world!")
     buf1 := make([]byte, 6)
        fmt.printf("reading index: %d\n", reader1.size()-int64(reader1.len()))
     
        reader1.read(buf1)
     fmt.println(string(buf1))
        fmt.printf("reading index: %d\n", reader1.size()-int64(reader1.len()))
        
     reader1.read(buf1)
     fmt.println(string(buf1))
        fmt.printf("reading index: %d\n", reader1.size()-int64(reader1.len()))
    }
    

    执行结果:

    reading index: 0
    hello
    reading index: 6
    world!
    reading index: 12

    可以看到,每读取一次之后,已读计数就会增加。

    strings包的readat方法不会依据已读计数进行读取,也不会更新已读计数。它可以根据偏移量来自由地读取reader值中的内容。

    package main
    
    import (
     "fmt"
     "strings"
    )
    func main() {
        reader1 := strings.newreader("hello world!")
        buf1 := make([]byte, 6)
     offset1 := int64(6)
     n, _ := reader1.readat(buf1, offset1) 
     fmt.println(string(buf2))
    }
    
    

    执行结果:

    world!

    也可以使用seek方法来指定下一次读取的起始索引位置。

    package main
    
    import (
     "fmt"
     "strings"
        "io"
    )
    func main() {
        reader1 := strings.newreader("hello world!")
        buf1 := make([]byte, 6)
     offset1 := int64(6)
     readingindex, _ := reader2.seek(offset1, io.seekcurrent)
     fmt.printf("reading index: %d\n", readingindex)
    
     reader1.read(buf1)
     fmt.printf("reading index: %d\n", reader1.size()-int64(reader1.len()))
     fmt.println(string(buf1))
    }
    
    

    执行结果:

    reading index: 6
    reading index: 12
    world!

    3、bytes.buffer

    bytes包和strings包类似,strings包主要面向的是 unicode 字符和经过 utf-8 编码的字符串,而bytes包面对的则主要是字节和字节切片,主要作为字节序列的缓冲区。bytes.buffer数据的读写都使用到了已读计数。

    bytes.buffer具有读和写功能,下面分别介绍他们的简单使用方法。

    3.1 bytes.buffer:写数据

    strings.builder一样,bytes.buffer可以用于拼接字符串,strings.builder也会自动对内容容器进行扩容。请看下面的代码:

    package main
    
    import (
     "bytes"
     "fmt"
    )
    
    func demobytes() {
     var buffer bytes.buffer
     buffer.writestring("hello ")
     buffer.writestring("world !")
     fmt.println(buffer.string())
    }
    
    

    执行结果:

    hello world !

    3.2 bytes.buffer:读数据

    bytes.buffer读数据也使用了已读计数,需要注意的是,进行读取操作后,len方法返回的是未读内容的长度。下面直接来看代码:

    package main
    
    import (
     "bytes"
     "fmt"
    )
    
    func demobytes() {
     var buffer bytes.buffer
     buffer.writestring("hello ")
     buffer.writestring("world !")
        
        p1 := make([]byte, 5)
     n, _ := buffer.read(p1)
        
     fmt.println(string(p1))
     fmt.println(buffer.string())
        fmt.printf("the length of buffer: %d\n", buffer.len())
    }
    

    执行结果:

    hello
     world !
    the length of buffer: 8

    4、字符串拼接

    简单了解了string类型、strings包和bytes.buffer类型后,下面来介绍golang中的字符串拼接方法。

    https://zhuanlan.zhihu.com/p/349672248

    go test -bench=. -run=^benchmarkdemobytes$

    4.1 直接相加

    最简单的方法是直接相加,由于string类型的值是不可变的,进行字符串拼接时会生成新的字符串,将拼接的字符串依次拷贝到一个新的连续内存空间中。如果存在大量字符串拼接操作,使用这种方法非常消耗内存。

    package main
    
    import (
     "bytes"
     "fmt"
     "time"
    )
    
    func main() {
     str1 := "hello "
     str2 := "world !"
        str3 := str1 + str2
        fmt.println(str3) 
    }
    
    

    4.2strings.builder

    前面介绍了strings.builder可以用于拼接字符串:

    var builder1 strings.builder
    builder1.writestring("hello ")
    builder1.writestring("world !")
    
    

    4.3 strings.join()

    也可以使用strings.join方法,其实join()调用了writestring方法;

    str1 := "hello "
    str2 := "world !"
    str3 := ""
    
    str3 = strings.join([]string{str3,str1},"")
    str3 = strings.join([]string{str3,str2},"")
    

    4.4 bytes.buffer

    bytes.buffer也可以用于拼接:

    var buffer bytes.buffer
    
    buffer.writestring("hello ")
    buffer.writestring("world !")
    

    4.5 append方法

    也可以使用go内置函数append方法,用于拼接切片:

    package main
    
    import (
     "fmt"
    )
    
    func demoappend(n int) {
     str1 := "hello "
     str2 := "world !"
     var str3 []byte
    
        str3 = append(str3, []byte(str1)...)
        str3 = append(str3, []byte(str2)...)
     fmt.println(string(str3))
    }
    
    

    执行结果:

    hello world !

    4.6 fmt.sprintf

    fmt包中的sprintf方法也可以用来拼接字符串:

    str1 := "hello "
    str2 := "world !"
    str3 := fmt.sprintf("%s%s", str1, str2)
    
    

    5、字符串拼接性能测试

    下面来测试一下这6种方法的性能,编写测试源码文件strcat_test.go

    package benchmark
    
    import (
     "bytes"
     "fmt"
     "strings"
     "testing"
    )
    
    func demobytesbuffer(n int) {
     var buffer bytes.buffer
    
     for i := 0; i < n; i++ {
      buffer.writestring("hello ")
      buffer.writestring("world !")
     }
    }
    
    func demowritestring(n int) {
     var builder1 strings.builder
     for i := 0; i < n; i++ {
      builder1.writestring("hello ")
      builder1.writestring("world !")
     }
    }
    
    func demostringsjoin(n int) {
     str1 := "hello "
     str2 := "world !"
     str3 := ""
     for i := 0; i < n; i++ {
      str3 = strings.join([]string{str3, str1}, "")
      str3 = strings.join([]string{str3, str2}, "")
     }
    
    }
    
    func demoplus(n int) {
    
     str1 := "hello "
     str2 := "world !"
     str3 := ""
     for i := 0; i < n; i++ {
      str3 += str1
      str3 += str2
     }
    }
    
    func demoappend(n int) {
    
     str1 := "hello "
     str2 := "world !"
     var str3 []byte
     for i := 0; i < n; i++ {
      str3 = append(str3, []byte(str1)...)
      str3 = append(str3, []byte(str2)...)
     }
    }
    
    func demosprintf(n int) {
     str1 := "hello "
     str2 := "world !"
     str3 := ""
     for i := 0; i < n; i++ {
      str3 = fmt.sprintf("%s%s", str3, str1)
      str3 = fmt.sprintf("%s%s", str3, str2)
     }
    }
    
    func benchmarkbytesbuffer(b *testing.b) {
     for i := 0; i < b.n; i++ {
      demobytesbuffer(10000)
     }
    }
    
    func benchmarkwritestring(b *testing.b) {
     for i := 0; i < b.n; i++ {
      demowritestring(10000)
     }
    }
    
    func benchmarkstringsjoin(b *testing.b) {
     for i := 0; i < b.n; i++ {
      demostringsjoin(10000)
     }
    }
    
    func benchmarkappend(b *testing.b) {
     for i := 0; i < b.n; i++ {
      demoappend(10000)
     }
    }
    
    func benchmarkplus(b *testing.b) {
     for i := 0; i < b.n; i++ {
      demoplus(10000)
     }
    }
    
    func benchmarksprintf(b *testing.b) {
     for i := 0; i < b.n; i++ {
      demosprintf(10000)
     }
    }
    
    
    执行性能测试:
    
    $ go test -bench=. -run=^$
    goos: windows
    goarch: amd64
    pkg: testgo/benchmark
    cpu: intel(r) core(tm) i7-8550u cpu @ 1.80ghz
    benchmarkbytesbuffer-8              3436            326846 ns/op
    benchmarkwritestring-8              4148            271453 ns/op
    benchmarkstringsjoin-8                 3         402266267 ns/op
    benchmarkappend-8                   1923            618489 ns/op
    benchmarkplus-8                        3         345087467 ns/op
    benchmarksprintf-8                     2         628330850 ns/op
    pass
    ok      testgo/benchmark        9.279s
    
    

    通过平均耗时可以看到writestring方法执行效率最高。sprintf方法效率最低。

    • 我们看到strings.join方法效率也比较低,在上面的场景下它的效率比较低,它在合并已有字符串数组的场合效率是很高的。
    • 如果要连续拼接大量字符串推荐使用writestring方法,如果是少量字符串拼接,也可以直接使用+。
    • append方法的效率也是很高的,它主要用于切片的拼接。
    • fmt.sprintf方法虽然效率低,但在少量数据拼接中,如果你想拼接其它数据类型,使用它可以完美的解决:
    name := "zhangsan"
    age := 20
    str4 := fmt.sprintf("%s is %d years old", name, age)
    fmt.println(str4)  // zhangsan is 20 years old
    
    

    到此这篇关于go语言中的字符串拼接方法详情的文章就介绍到这了,更多相关go语言中的字符串拼接方法内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!