string类型定义

位于src/builtin/builtin.go

  1. string是8比特字节的集合
  2. string可以为空(长度为0),但不会是nil;
  3. string对象不可以修改。
// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
// 翻译
// 字符串是所有 8 位字节字符串的集合,通常但不是
// 必须代表 UTF-8 编码的文本。字符串可能为空,但
// 不是零。字符串类型的值是不可变的。
type string string

string 数据结构

位于src/runtime/string.go:stringStruct

type stringStruct struct {
    str unsafe.Pointer 		//字符串首地址
    len int					//字符串长度
}

字符串构建过程是先根据字符串构建stringStruct,再转换成string。转换的源码如下:

func gostringnocopy(str *byte) string { // 根据字符串地址构建string
    ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)} // 先构造stringStruct
    s := *(*string)(unsafe.Pointer(&ss))                             // 再将stringStruct转换成string
    return s
}

string在runtime包中就是stringStruct,对外呈现叫做string。

string与[]byte转换

[]byte转换string

  1. 获取切片长度,申请内存空间
  2. 构建string
  3. copy数据

string转换[]byte

  1. 申请切片内存空间
  2. 将string拷贝到切片

字符串拼接

性能上也有比较好的保证,因为新字符串的内存空间是一次分配完成的,所以性能消耗主要在拷贝数据上。

一个拼接语句的字符串编译时都会被存放到一个切片中,拼接过程需要遍历两次切片,第一次遍历获取总的字符串长度,据此申请内存,第二次遍历会把字符串逐个拷贝过去。

func concatstrings(a []string) string { // 字符串拼接
    length := 0        // 拼接后总的字符串长度

    for _, str := range a {
        length += len(str)
    }

    s, b := rawstring(length) // 生成指定大小的字符串,返回一个string和切片,二者共享内存空间

    for _, str := range a {
        copy(b, str)    // string无法修改,只能通过切片修改
        b = b[len(str):]
    }

    return s
}

像C++语言中的string,其本身拥有内存空间,修改string是支持的。但Go的实现中,string不包含内存空间,只有一个内存的指针,这样做的好处是string变得非常轻量,可以很方便的进行传递而不用担心内存拷贝。

因为string通常指向字符串字面量,而字符串字面量存储位置是只读段而不是堆或栈上,所以才有了string不可修改的约定

[]byte转换成string不一定会拷贝内存

string和[]byte如何取舍

string和[]byte都可以表示字符串,但因数据结构不同,其衍生出来的方法也不同,要根据实际应用场景来选择。

string 擅长的场景:

  • 需要字符串比较的场景;
  • 不需要nil字符串的场景;

[]byte擅长的场景:

  • 修改字符串的场景,尤其是修改粒度为1个字节;
  • 函数返回值,需要用nil表示含义的场景;
  • 需要切片操作的场景;

虽然看起来string适用的场景不如[]byte多,但因为string直观,在实际应用中还是大量存在,在偏底层的实现中[]byte使用更多。