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 使用更多。