go

Go 系列 数据结构-string

Posted by lichao modified on July 21, 2022

Go标准库 builtin 给出了所有内置类型的定义。源代码位于 src/builtin/builtin.go ,其中关于string的描述如下:

1
2
3
4
// 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. 
type string string 

所以 string 是 8 比特字节的集合,通常但并不一定是 UTF-8 编码的文本。

另外,还提到了两点,非常重要:

  • string 可以为空(长度为 0),但不会是 nil;
  • string 对象不可以修改。

为什么字符串不允许修改?

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

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

字符串是单独放在一个存储位置上, 非堆非栈, 是虚拟内存分区的只读段。

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

byte 切片转换成 string 的场景很多,为了性能上的考虑,有时候只是临时需要字符串的场景下,byte 切片转换成 string 时并不会拷贝内存,而是直接返回一个 string,这个 string 的指针(string.str)指向切片的内存。

比如,编译器会识别如下临时场景:

  • 使用 m[string(b)] 来查找 map(map 是 string 为 key,临时把切片 b 转成 string);
  • 字符串拼接,如 ”<” + “string(b)” + “>”;
  • 字符串比较:string(b) == “foo”

因为是临时把 byte 切片转换成 string,也就避免了因 byte 切片内容改变而导致 string 引用失败的情况,所以此时可以不必拷贝内存新建一个 string。

string和[]byte如何取舍

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

string 擅长的场景:

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

[]byte 擅长的场景:

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

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