Go的标准库builtin给出了所有内置类型的定义。源代码位于src/builtin/builtin.go,其中关于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.
type string stringstring是8比特字节的集合,通常但不一定是UTF-8编码的文本。
- string可以是空,但不会是nil。
- string对象不可修改
数据结构
在src/runtime/string.go:stringStruct定义了string的数据结构。
type stringStruct struct {
str unsafe.Pointer
len int
}可以看到string的数据结构很简单
- str:指向字符串首地址的指针
- len:字符串的长度
string跟切片的结构非常相似,只不过切片还有一个表示容量的成员,事实上,string和byte切片经常发生转换。
创建string
string的创建只需要简单的声明即可
var str string
str = "hello world"实际的构建过程时先根据字符串构建stringStruct,再转换成string,源码如下:
func gostringncopy(str *byte) string {
ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
s := *(*string)(unsafe.Pointer(&ss))
return s
}string在runtime包中就是stringStruct,对外呈现的叫做string。
类型转换
byte切片转string
func GetStringBySlice(s []byte) string {
return string(s)
}需要注意这种转换需要一次内存拷贝。
- 根据切片的长度申请内存空间,假设内存地址为p,切片长度为len(b)
- 构建string
- 拷贝数据,将切片中数据拷贝到新申请的内存空间中。

string转byte切片
func GetSliceByString(str strng) []byte {
return []byte(str)
}- 申请切片内存空间
- 将string拷贝到切片
所以这种转换过程中也发生了一次内存拷贝。

byte切片转换为string不拷贝内存的场景
前面的两种转换都需要进行一次内存拷贝,为了性能上的考虑,在临时需要字符串的场景下不会拷贝内存,而是直接返回一个string。
比如:
- 使用
m[string(b)]来查找map - 字符串拼接,如
"<" + string(b) + ">" - 字符串比较:
string(b) == "foo"这种临时使用的场景,只是暂时读取内存来使用,没有必要拷贝内存新建一个string。
字符串的拼接
使用加号可以很方便的进行拼接
str := "str1" + "str2" + "str3"字符串是不可变的,所以拼接字符串其实就是创建一个新的字符串的过程。
不过GO语言对字符串的拼接进行了优化,纵使+拼接的字符串串很多,也具有较好的性能:将新字符串的内存空间一次分配完成。 在编译的时候拼接语句的字符串都会放到一个切片中,拼接过程需要遍历两次切片,第一次获得字符串的总长度然后据此申请内存,第二次将字符串逐个拷贝过去。
这个过程用伪代码表示就是:
func concatstrings(a []string) string {
lenght := 0
for _, str := range a {
length += lenght(str)
}
// 生成指定大小的字符串,返回一个string和切片,两者共享内存
// 所以修改切片也就修改了字符串的内容
s, b := rawstring(length)
for _, str := range a {
copy(b, str)
b = b[len(str):]
}
return s
}
func rawstring(size int) (s string ,b []byte) {
p := mallocgc(uintptr(size), nil, false)
stringStructOf(&s).str = p
stringStructOf(&s).len = size
*(*slice)(unsafe.Pointer(&b)) = slice(p, size, size)
return
}字符串为什么不允许修改
像C++这种语言的字符串,自身拥有内存空间的,是支持修改string的。
但是Go的实现中,string不包含内存空间,只是有一个内存的指针,这样使得string变得很轻量,进行传递而不用担心内存拷贝。
因为string通常指向字面量,而字面量存储位置是只读段,而不是堆或者栈上,所以有了string不可修改的约定。
string和byte切片的选择
string使用的场景:
- 需要字符串比较的场景
- 不需要nil字符串的场景
byte切片使用的场景:
- 修改字符串的场景,尤其是修改粒度为1个字节。
- 函数返回值,需要用nil表示含义的场景
- 需要切片操作的场景。 因为string的不可变性,所以实际使用的场景中还是byte切片使用的多,并且在偏底层的实现上要尤为明显。