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