【问题标题】:How can I clone a strings.Builder in Go?如何在 Go 中克隆 strings.Builder?
【发布时间】:2021-01-12 19:47:44
【问题描述】:

Go 编程语言的标准库公开了一个名为 strings.Builder 的结构,它允许以一种有效的方式通过重复连接轻松构建字符串,类似于 C# 或 Java 的 StringBuilder

在 Java 中,我会使用 StringBuilder 的构造函数来“克隆”对象,如下所示:

StringBuilder newBuffer = new StringBuilder(oldBuffer.toString());

在Go中,我只能看到以下两行方式:

newBuffer := strings.Builder{}
newBuffer.WriteString(oldBuffer.String())

没有其他 .Clone() 初始化方法(我可能还没有找到)。

有没有比我介绍的更简洁/简洁的另一种方式?

【问题讨论】:

  • 当然。添加您自己的函数以及克隆字符串生成器所需的 两行,然后只需一行即可为调用者克隆。
  • WriteString 方法可能会失败,因此它返回两个值,一个字符串和一个错误。我不确定这些是如何在 C# 或 Java 中实现的,但我想如果它们也可能失败,则需要某种异常处理。
  • @Grizzle WriteString 方法被声明为返回错误,以便 StringBuilder 满足 io.StringWriter 接口,而不是因为 WriteString 可能会失败。不需要错误检查。
  • @Gavin 当然,这将是最后的选择......我只是想检查一下我是否缺少另一种更标准/惯用的形式。很抱歉因为我的好奇心打扰了您!
  • 如果你查看builder.go的源代码,你会看到很多函数有3行长(克隆这个字符串生成器的函数长度相同,当你计算return ...)。您是否还会主张只在使用它们的任何地方编写其他两组 2 行? (只是一些思考的食物)

标签: go


【解决方案1】:

出于好奇而进入不必要的细节......

在考虑the documentation 之后,以下是您的主要问题:

  1. 从 Builder 中读取数据的唯一导出方法是 Builder.String 方法。
  2. 一旦操作了Builder 值,复制它是不安全的。

让我们看看这个版本:

newBuffer := strings.Builder{}
newBuffer.WriteString(oldBuffer.String())

我首先想到为什么这是不可取的,因为Builder 在内部使用字节切片(可变数据类型),并返回一个字符串(不可变数据类型)。尽管字符串的底层表示与字节切片相同,但由于这种可变性规则,它需要一个副本才能转换为字符串。这意味着当您将字符串写入新缓冲区时,您已经在第二个副本上,而您的任务直观地只需要一个副本。

实际上看一下source code,然而,我们会发现这个假设是错误的:

func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

使用unsafe 包,strings 包基本上将缓冲区 ([]byte) 直接“破解”为一个`字符串。同样,这些数据类型在内存级别上是相同的:指向字符串或切片开头的指针,以及描述字符串或切片长度的指针偏移量。这些数据类型只是标题,所以这里没有发生缓冲区的复制。

这会造成一种不舒服的情况,即您有一个应该是不可变的字符串,但您仍然有一个字节切片 somewhere 可以改变这些底层字节。毕竟这个包叫做unsafe,这是一个很好的例子。

因为strings.Builder 纯粹是一个“构建器”,即它只能创建字符串的新部分而不能修改已经写入的数据,所以我们仍然可以得到语言“保证”的字符串的不变性。打破该规则的唯一方法是访问Builder 的内部buf,但作为field is un-exported,您需要再次使用unsafe 才能访问它。

总结:

您想出的直接方法,虽然可能比人们希望的要长一行(或两),但它是确定且正确的方法。它已经与您将获得的一样高效,即使您带出了 Go 的更多坚韧不拔的功能,例如 unsafereflect

我希望这可以提供信息。以下是对您的代码的唯一建议更改:

// clone the builder contents. this is fast.
newBuffer := strings.Builder{}
newBuffer.WriteString(oldBuffer.String())

【讨论】:

  • 谢谢!我猜想在strings.Builder{} 之外无法真正访问底层字节切片,因此.String() 方法内部的不安全调用可能没什么大不了的。很高兴知道它不会做两份而不是一份。写得好,再次感谢!
猜你喜欢
  • 2017-09-17
  • 2018-10-08
  • 2016-03-31
  • 2021-11-11
  • 2012-09-30
  • 2012-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多