切片后释放字符串以进行垃圾回收的正确方法

5
根据这篇 Go数据结构文章,在字符串部分指出,对一个字符串进行切片操作会在内存中保留原始字符串。

"(顺便提一下,在Java和其他语言中有个常见的陷阱,当你切割一个字符串以保存一小部分时,对原始引用的保留会使整个原始字符串仍然存在于内存中,即使只需要很少的一部分。Go也有这个陷阱。我们尝试过并拒绝了另一种选择,即使字符串切片非常昂贵——需要分配和复制——大多数程序都会避免它。)"

因此,如果我们有一个非常长的字符串:

s := "Some very long string..."

我们只提取一小部分:

newS := s[5:9]

在我们发布newS之前,原始的s将不会被释放。鉴于此,如果我们需要长期保留newS,但需要释放s进行垃圾回收,应该采取什么适当的方法?

我想也许可以尝试以下方法:

newS := string([]byte(s[5:9]))

但我并不确定那样做是否有效,或者有更好的方法。


2
应该注意的是,Java 放弃了这种优化,因为相对于用户代码中添加的错误而言,收益太低了。请参见此相关问题。在 Go 中,问题是不同的,因为数组切片具有非常特定的语义,应该作为语言基础学习。 - Denys Séguret
2个回答

5

是的,将其转换为字节数组会创建字符串的副本,因此原始字符串不再被引用,并且可以在以后的某个时候进行垃圾回收。

作为这一点的“证明”(好吧,它证明了字节数组与原始字符串不共享相同的基础数据):

http://play.golang.org/p/pwGrlETibj

编辑:并证明字节片只有必要的长度和容量(换句话说,它的容量不等于原始字符串的容量):

http://play.golang.org/p/3pwZtCgtWv

编辑2:您可以清楚地看到内存分析的情况。在reuseString()中,使用的内存非常稳定。在copyString()中,它快速增长,显示了[]byte转换所做的字符串副本。

http://play.golang.org/p/kDRjePCkXq


在你的示例中,新的[]byte被分配给一个变量而不是像我的一样内联。这会有所不同吗? - the system
不是,查看我的答案中的第二个编辑(通过内存统计数据进行证明)。 - mna
而之前的Go问题(和答案)是关于这种行为的,所以您可能想看一下https://dev59.com/k2Yq5IYBdhLWcg3wkRe_#14373714 - mna

3
确保在对字符串进行切片并保持该切片“活动”后,最终可能会使其有资格进行垃圾回收的正确方法是创建一个切片的副本并保持副本“活动”。但现在,人们正在以时间性能恶化的代价购买更好的内存性能。这在某些地方可能很好,但在其他地方可能是 邪恶 的。有时只有正确的测量,而不是猜测,才能告诉我们真正的收益在哪里。
例如,当我喜欢一点邪恶的时候,我使用 StrPack

谢谢。是的,我心里想的是从一个大字符串中取出一个非常小的片段,而这个片段在其他情况下并不需要。主要是想知道正确的技巧。我看到 StrPack 也使用了同样的方法。再次感谢。 - the system
1
你是指 newS := StrPack(s[5:9]) 吗?;-) - zzzz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接