如何(深)复制Go中的字符串?
我可能应该先解释为什么我想要那个。
我明白在 Go substring( s[i:j]) 和string.Split其他一些字符串操作就地工作:结果子字符串共享原始字符串的相同内存块。
例如,我读取一个大字符串,解析并从中获取一些子字符串,这些子字符串将长期保存在服务器程序中,它们将“保留”来自 GC 的大内存块,浪费内存。我假设如果我可以制作这些子字符串的副本并保留这些副本,GC 可以释放那个大字符串。
但是我在 Go 中找不到字符串复制机制,我尝试再次将其转换为[]bytethen string,在我的特定用例中,内存使用量下降了大约 3/4。
但这感觉不对:1,它引入了两个复制操作。第二,由于我从未真正写入该字节片,我怀疑它可能会在发布版本中得到优化。
我无法想象以前没有人问过这个问题,但是我的搜索没有产生任何相关结果,或者是否有一些更好的实践可以在 Go 中做这些事情?
顺便说一句,我试图向它附加一个空字符串(+""),内存消耗不会下降,我认为即使在测试版本中它也得到了优化。
为了测量内存使用情况,我调用runtime.GC()thenruntime.ReadMemStats()和 compare MemStats.Alloc,这在我的测试中似乎非常一致。
回答
该字符串被实现为指向底层字节数组和字符串长度的指针。当您从现有字符串创建切片时,新字符串仍指向底层数组,可能指向该数组中的不同偏移量,具有不同的长度。这样,许多小字符串可以使用单个底层大数组。
正如您所指出的,如果您有一个大字符串并解析它以获得更小的字符串,您最终会将大字符串保留在内存中,因为 GC 只知道底层数组和指向它的指针。有两种方法可以解决这个问题:
- 保留一个
[]byte或使用基于字节流的读取器/扫描器,而不是大字符串,并在解析输入时创建字符串。这样 GC 将[]byte在解析完成时收集底层,并且您将拥有没有底层大块的字符串。 - 执行您已经描述的操作,并使用
string([]byte(s[x:y]))或 使用copy.
回答
go 中的字符串一旦创建就是不可变的。去规格
我更喜欢下面的构建器。您继续添加到构建器的缓冲区(可变),WriteString 并且一旦完成调用String方法,该方法返回指针而不是缓冲区切片的另一个副本。
somestring := "Hello Go"
var sb strings.Builder
if _, err := sb.WriteString(somestring); err != nil {
//log & return
}
newstring := sb.String()
从 go 源检查 builder 的 String() 的实现。它正在返回指针并转换为 *string。没有第二个副本。
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}