• 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
    ", s)
        fmt.Printf("ptr of str: %p
    ", &s)
        // 修改, 将,修改为!
        bs := []byte(str)
        bs[2] = '!' 
        fmt.Printf("value of lstr: %v
    ", string(bs))
        fmt.Printf("ptr of lstr: %p
    ", &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!
    }
    

  • 相关阅读:
    Linux添加PATH,即命令自动补全功能
    编程的奥义
    Windows SDK笔记
    一个烂电源,毁了我一天的工作
    贪念
    VC菜菜鸟创建一个即时串口通信程序
    IP视频监控系统开放式平台的5个标志
    敷铜的入门
    解决:Ulead VideoStudio 启动黑屏的问题
    数据结构中,关于“大小端”的数据存储方式的相关问题
  • 原文地址:https://www.cnblogs.com/vinsent/p/11281777.html
Copyright © 2020-2023  润新知