字符串切片是否执行基础数据的复制?



我正在尝试使用 utf8 库有效地计算 utf-8string中的符文。 此示例是否最佳,因为它不复制基础数据?
https://golang.org/pkg/unicode/utf8/#example_DecodeRuneInString

func main() {
str := "Hello, 世界" // let's assume a runtime-provided string
for len(str) > 0 {
r, size := utf8.DecodeRuneInString(str)
fmt.Printf("%c %vn", r, size)
str = str[size:] // performs copy?
}
}

我在(不安全的(反射库中发现了 StringHeader。 这是 Go 中string的确切结构吗? 如果是这样,可以想象切片字符串只是更新Data或完全分配新的StringHeader

type StringHeader struct {
Data uintptr
Len  int
}

奖励:我在哪里可以找到执行string切片的代码,以便我可以自己查找? 这些中的任何一个?

https://golang.org/src/runtime/slice.go https://golang.org/src/runtime/string.go

这个相关的 SO 答案表明,运行时字符串在从string转换为[]byte时会产生副本。

切片字符串

字符串切片是否执行基础数据的复制?

不,它没有。 请参阅Russ Cox的这篇文章:

字符串

在内存中表示为包含指向字符串数据的指针和长度的 2 字结构。由于字符串是不可变的,因此多个字符串共享同一存储是安全的,因此切片 s 会导致一个新的 2 字结构,其指针和长度可能不同,仍引用相同的字节序列。这意味着切片可以在没有分配或复制的情况下完成,使字符串切片与传递显式索引一样高效。

-- Go 数据结构

切片、性能和迭代符文

切片基本上是三件事:长度、容量和指向底层数组中位置的指针。

因此,切片本身不是很大:int 和指针(可能是实现细节中的其他一些小东西(。 因此,创建切片副本所需的分配非常小,并且不依赖于底层数组的大小。 当您简单地更新长度、容量和指针位置时,不需要新的分配,例如在以下行的第 2 行:

foo := []int{3, 4, 5, 6}
foo = foo[1:]

相反,当必须分配新的底层阵列时,才会感受到性能影响。

Go 中的字符串是不可变的。 因此,要更改字符串,您需要创建一个新字符串。 但是,字符串与字节片密切相关,例如,您可以使用

foo := `here's my string`
fooBytes := []byte(foo)

我相信这将分配一个新的字节数组,因为:

字符串实际上是字节的只读切片

根据 Go 博客(参见 Go 中的字符串、字节、符文和字符(。 通常,您可以使用切片来更改底层数组的内容,因此要从字符串生成可用的字节切片,您必须制作副本以防止用户更改应该是不可变的内容。

您可以使用性能分析和基准测试来进一步了解程序的性能。

一旦你有了你的字节片,fooBytes,重新切片不会分配一个新的数组,它只是分配一个新的切片,这个片很小。 这似乎也是切片字符串的作用。

请注意,您不需要使用utf8包来计算 utf8 字符串中的单词,但如果您愿意,您可以继续这样做。 Go 在本地处理 utf8。 但是,如果要迭代字符,则不能将字符串表示为字节片,因为可以包含多字节字符。 相反,您需要将其表示为符文的一部分:

foo := `here's my string`
fooRunes := []rune(foo)

根据我的经验,这种将字符串转换为符文切片的操作很快(在我完成的基准测试中微不足道,但可能会有分配(。 现在,您可以遍历fooRunes来计算字数,无需utf8包。 或者,您可以跳过显式[]rune(foo)转换,并通过对字符串使用for ... range循环隐式执行,因为这些是特殊的:

相比之下,for 范围循环在每次迭代时解码一个 UTF-8 编码的符文。每次循环时,循环的索引都是当前符文的起始位置,以字节为单位,代码点是它的值。

-- Go 中的字符串、字节、符文和字符

相关内容

最新更新