Go白皮书并没有说明一个子字符串表达式的结果(子)字符串和基础字符串是否应该共享一个承载底层字节序列的内存块。 但标准编译器确实让它们共享一个内存块,而且很多标准库包的函数原型设计也默认了这一点。 这是一个好的设计,它不仅节省内存,而且还减少了CPU消耗。 但是有时候它会造成暂时性的内存泄露。
demos0
var s0 string // 一个包级变量
// 一个演示目的函数。
func f(s1 string) {
s0 = s1[:50]
// 目前,s0和s1共享着承载它们的字节序列的同一个内存块。
// 虽然s1到这里已经不再被使用了,但是s0仍然在使用中,
// 所以它们共享的内存块将不会被回收。虽然此内存块中
// 只有50字节被真正使用,而其它字节却无法再被使用。
}
func demo() {
s := createStringWithLengthOnHeap(1 << 20) // 1M bytes
f(s)
}
f
func f(s1 string) {
s0 = string([]byte(s1[:50]))
}
此种防止临时性内存泄露的方法不是很高效,因为在此过程中底层的字节序列被复制了两次,其中一次是不必要的。
我们可以利用官方Go编译器对字符串衔接所做的优化来防止一次不必要的复制,代价是有一个字节的浪费。
func f(s1 string) {
s0 = (" " + s1[:50])[1:]
}
此第二种防止临时性内存泄露的方法有可能在将来会失效,并且它对于其它编译器来说很可能是无效的。
strings.Builder
import "strings"
func f(s1 string) {
var b strings.Builder
b.Grow(50)
b.WriteString(s1[:50])
s0 = b.String()
}
stringsRepeatstrings.Builder