为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
MMTTMM 2022-07-25 10:21:26
我可能应该先解释一下为什么我想要那个。我理解 Go substring( s[i:j]) 和string.Split其他一些字符串操作就地工作:生成的子字符串共享原始字符串的相同内存块。例如,我读取一个大字符串,解析并从中获取一些子字符串,这些子字符串将长期保存在服务器程序中,它们将“持有”来自 GC 的大内存块,浪费内存。我假设如果我可以复制这些子字符串并保留这些副本,GC 可以释放那个大字符串。但是我在 Go 中找不到字符串复制机制,我尝试再次将其转换为[]bytethen string,在我的特定用例中内存使用量下降了大约 3/4。但这感觉不对:第一,它引入了两个复制操作。第二,由于我从未真正写入该字节片,我怀疑它可能会在发布版本中得到优化。我无法想象以前没有人问过这个问题,但是我的搜索没有产生任何相关的结果,或者在 Go 中有一些更好的做法来做这些事情吗?顺便说一句,我尝试向+""它附加一个空字符串(),内存消耗并没有下降,我认为即使在测试版本中它也得到了优化。为了测量内存使用情况,我调用runtime.GC()thenruntime.ReadMemStats()和 compare MemStats.Alloc,这在我的测试中似乎非常一致。
查看完整描述

4 回答

?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

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))

}


查看完整回答
反对 回复 2022-07-25
?
慕田峪7331174

TA贡献1828条经验 获得超13个赞

另一种选择是,从 go 1.18(2021 年第四季度)开始:

sCopy := strings.Clone(s)

它来自问题4020045038,并从 CL(更改列表)334884345849 开始

字节,字符串:添加克隆

直接使用[]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))

}


查看完整回答
反对 回复 2022-07-25
?
红颜莎娜

TA贡献1842条经验 获得超12个赞

字符串被实现为指向底层字节数组和字符串长度的指针。当您从现有字符串创建切片时,新字符串仍指向基础数组,可能指向该数组中具有不同长度的不同偏移量。这样,许多小字符串可以使用单个底层大数组。

正如您所指出的,如果您有一个大字符串并将其解析为较小的字符串,那么您最终会将大字符串保留在内存中,因为 GC 只知道底层数组和指向它的指针。有两种方法可以处理这个问题:

  • 不要使用大字符串,而是保留[]byte或使用基于字节流的读取器/扫描器,并在解析时从输入创建字符串。这样 GC 将[]byte在解析完成时收集底层,并且您将拥有没有底层大块的字符串。

  • 执行您已经描述的操作,并使用string([]byte(s[x:y]))或使用copy.


查看完整回答
反对 回复 2022-07-25
?
慕无忌1623718

TA贡献1744条经验 获得超4个赞

使用以下函数深度复制字符串:


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 错误。


查看完整回答
反对 回复 2022-07-25
  • 4 回答
  • 0 关注
  • 142 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信