猿问

如何(深度)在 Go 中复制字符串?

我可能应该先解释一下为什么我想要那个。

我理解 Go substring( s[i:j]) 和string.Split其他一些字符串操作就地工作:生成的子字符串共享原始字符串的相同内存块。

例如,我读取一个大字符串,解析并从中获取一些子字符串,这些子字符串将长期保存在服务器程序中,它们将“持有”来自 GC 的大内存块,浪费内存。我假设如果我可以复制这些子字符串并保留这些副本,GC 可以释放那个大字符串。

但是我在 Go 中找不到字符串复制机制,我尝试再次将其转换为[]bytethen string,在我的特定用例中内存使用量下降了大约 3/4。

但这感觉不对:第一,它引入了两个复制操作。第二,由于我从未真正写入该字节片,我怀疑它可能会在发布版本中得到优化。

我无法想象以前没有人问过这个问题,但是我的搜索没有产生任何相关的结果,或者在 Go 中有一些更好的做法来做这些事情吗?

顺便说一句,我尝试向+""它附加一个空字符串(),内存消耗并没有下降,我认为即使在测试版本中它也得到了优化。

为了测量内存使用情况,我调用runtime.GC()thenruntime.ReadMemStats()和 compare MemStats.Alloc,这在我的测试中似乎非常一致。


MMTTMM
浏览 171回答 4
4回答

天涯尽头无女友

Go 中的字符串一旦创建就不可变。去规范我会更喜欢下面的builder。您继续添加到构建器的缓冲区(可变)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))}

慕田峪7331174

另一种选择是,从 go 1.18(2021 年第四季度)开始:sCopy := strings.Clone(s)它来自问题40200和45038,并从 CL(更改列表)334884和345849 开始字节,字符串:添加克隆直接使用[]byte并且string很常见并且需要经常复制它们。此更改Clone为字符串和字节添加了一个助手来满足此需求。还添加了一个基准,以提供证据说明为什么bytes.Clone使用copy.字符串:添加克隆功能新strings.Clone函数复制输入字符串,但返回的克隆字符串不引用输入字符串内存看strings/clone.go// Clone returns a fresh copy of s.//// It guarantees to make a copy of s into a new allocation,// which can be important when retaining only a small substring// of a much larger string. Using Clone can help such programs// use less memory. //// Of course, since using Clone makes a copy,// overuse of Clone can make programs use more memory.//// Clone should typically be used only rarely, and only when// profiling indicates that it is needed.//func Clone(s string) string {    b := make([]byte, len(s))    copy(b, s)    return *(*string)(unsafe.Pointer(&b))}

红颜莎娜

字符串被实现为指向底层字节数组和字符串长度的指针。当您从现有字符串创建切片时,新字符串仍指向基础数组,可能指向该数组中具有不同长度的不同偏移量。这样,许多小字符串可以使用单个底层大数组。正如您所指出的,如果您有一个大字符串并将其解析为较小的字符串,那么您最终会将大字符串保留在内存中,因为 GC 只知道底层数组和指向它的指针。有两种方法可以处理这个问题:不要使用大字符串,而是保留[]byte或使用基于字节流的读取器/扫描器,并在解析时从输入创建字符串。这样 GC 将[]byte在解析完成时收集底层,并且您将拥有没有底层大块的字符串。执行您已经描述的操作,并使用string([]byte(s[x:y]))或使用copy.

慕无忌1623718

使用以下函数深度复制字符串:func deepCopy(s string) string {    b := make([]byte, len(s))    copy(b, s)    return *(*string)(unsafe.Pointer(&b))}该函数将数据复制到新分配的字节片中。该函数使用 unsafe 包将切片标头转换为不复制字节的字符串标头。如果直接使用 unsafe 包是一个问题,那么使用strings.Builder。strings.Builder 类型在幕后执行不安全的恶作剧。 func deepCopy(s string) string {     var sb strings.Builder     sb.WriteString(s)     return sb.String() }无需检查 sb.WriteString 返回的错误。Builder.WriteString方法有错误返回,所以 Builder 类型满足 io.StringWriter接口,而不是因为 WriteString 可以返回非 nil 错误。
随时随地看视频慕课网APP

相关分类

Go
我要回答