我正在尝试使用string
库从utf-8 utf8有效地计算符文。此示例是否最佳,因为它不复制基础数据?
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?
}
}
我在(不安全)反射库中找到了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
这个related SO answer表明运行时字符串在从string
转换为[]byte
时会产生副本。
片段切片执行底层数据的复制吗?
不,不是的。请参阅Russ Cox的这篇文章:
字符串在内存中表示为包含指向字符串数据和长度的指针的2字结构。因为字符串是不可变的,所以多个字符串共享相同的存储是安全的,因此切片会产生一个新的双字结构,其中可能有不同的指针和长度仍然指向相同的字节序列。这意味着可以在不分配或复制的情况下完成切片,从而使字符串切片与传递显式索引一样高效。
切片基本上是三件事:长度,容量和指向底层数组中某个位置的指针。
因此,切片本身不是很大:int和指针(实现细节中可能还有其他一些小东西)。因此,制作切片副本所需的分配非常小,并且不依赖于底层数组的大小。当您只是更新长度,容量和指针位置时,不需要新的分配,例如在第2行:
foo := []int{3, 4, 5, 6}
foo = foo[1:]
相反,它必须分配一个新的底层数组,以便感受到性能影响。
Go中的字符串是不可变的。因此,要更改字符串,您需要创建一个新字符串。但是,字符串与字节切片密切相关,例如,你可以用字符串创建一个字节切片
foo := `here's my string`
fooBytes := []byte(foo)
我相信会分配一个新的字节数组,因为:
一个字符串实际上是一个只读的字节片
根据Go Blog(见Strings, bytes, runes and characters in Go)。通常,您可以使用切片来更改基础数组的内容,因此要从字符串生成可用的字节切片,您必须制作副本以防止用户更改应该是不可变的内容。
您可以使用performance profiling和benchmarking来进一步了解程序的性能。
一旦你获得了切片的字节,fooBytes
,它重新分配它不会分配一个新的数组,它只是分配一个新的切片,这是一个很小的。这似乎也是切割字符串的方式。
请注意,您不需要使用utf8
包来计算utf8字符串中的单词,但如果您愿意,可以按照这种方式进行计数。 Go本地处理utf8。但是,如果要迭代字符,则无法将字符串表示为字节切片,因为您可以使用多字节字符。相反,你需要将它表示为一片符文:
foo := `here's my string`
fooRunes := []rune(foo)
根据我的经验,这种将字符串转换为符文片段的操作很快(我已经完成了基准测试,但可能会有分配)。现在你可以遍历fooRunes
来计算单词,不需要utf8
包。或者,您可以跳过显式的[]rune(foo)
转换,并通过在字符串上使用for ... range
循环隐式执行,因为这些是特殊的:
相反,对于范围循环,在每次迭代时解码一个UTF-8编码的符文。每次循环时,循环的索引是当前符文的起始位置,以字节为单位,代码点是其值。