字符串切片是否执行底层数据的复制?

Raz*_*azl 6 string utf-8 go slice

我正在尝试string使用utf8库有效地计算 utf-8中的符文。此示例是否最佳,因为它不复制基础数据?
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 %v\n", r, size)
        str = str[size:] // performs copy?
    }
}
Run Code Online (Sandbox Code Playgroud)

我在(不安全的)反射库中找到了StringHeader。这是stringGo中 a 的确切结构吗?如果是这样,可以想象,对字符串进行切片只是更新Data或分配一个新的字符串StringHeader

type StringHeader struct {
        Data uintptr
        Len  int
}
Run Code Online (Sandbox Code Playgroud)

奖励:在哪里可以找到执行string切片的代码,以便我可以自己查找?有这些吗?
https://golang.org/src/runtime/slice.go
https://golang.org/src/runtime/string.go

相关SO答案,从转化时表明运行时字符串招致拷贝string[]byte

jre*_*ior 5

切片字符串

字符串切片是否执行底层数据的复制?

不,不是的。请参阅 Russ Cox 的这篇文章:

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

--去数据结构

切片、性能和迭代符文

切片基本上是三样东西:长度、容量和指向底层数组中某个位置的指针。

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

foo := []int{3, 4, 5, 6}
foo = foo[1:]
Run Code Online (Sandbox Code Playgroud)

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

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

foo := `here's my string`
fooBytes := []byte(foo)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

foo := `here's my string`
fooRunes := []rune(foo)
Run Code Online (Sandbox Code Playgroud)

根据我的经验,这种将字符串转换为符文片段的操作很快(在我所做的基准测试中微不足道,但可能会有分配)。现在您可以迭代fooRunes计算单词,无需任何utf8包。或者,您可以跳过显式[]rune(foo)转换并通过for ... range在字符串上使用循环来隐式转换,因为它们很特殊:

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

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