以字节形式访问字符串元素是否执行转换?

Does accessing elements of string as byte perform conversion?

在 Go 中,要访问 string 的元素,我们可以这样写:

str := "text"
for i, c := range str {
  // str[i] is of type byte
  // c is of type rune
}

当访问 str[i] 时,Go 是否执行从 runebyte 的转换?我想答案是肯定的,但我不确定。如果是这样,那么以下哪一种方法在性能方面更好?是否一个人比另一个人更受欢迎(例如,就最佳实践而言)?

str := "large text"
for i := range str {
  // use str[i]
}

str := "large text"
str2 := []byte(str)
for _, s := range str2 {
  // use s
}
Go 中的

string 值存储文本的 UTF-8 编码字节,而不是其字符或 runes。

Indexing a string 索引其字节:str[i] 是类型 byte(或 uint8,它是别名)。此外,string 实际上是一个只读的字节片段(带有一些语法糖)。索引 string 不需要将其转换为切片。

当您在 string 上使用 for ... range 时,会迭代 stringrune,而不是它的字节!

因此,如果您想遍历 runes(字符),则必须使用 for ... range,但不能转换为 []byte,因为第一种形式无法使用string 值包含多 (UTF-8) 字节字符。 string 值上的 spec allows youfor ... range,第一个迭代值将是当前字符的字节索引,第二个值将是 [= 类型的当前字符值13=](int32 的别名):

For a string value, the "range" clause iterates over the Unicode code points in the string starting at byte index 0. On successive iterations, the index value will be the index of the first byte of successive UTF-8-encoded code points in the string, and the second value, of type rune, will be the value of the corresponding code point. If the iteration encounters an invalid UTF-8 sequence, the second value will be 0xFFFD, the Unicode replacement character, and the next iteration will advance a single byte in the string.

简单示例:

s := "Hi 世界"
for i, c := range s {
    fmt.Printf("Char pos: %d, Char: %c\n", i, c)
}

输出(在 Go Playground 上尝试):

Char pos: 0, Char: H
Char pos: 1, Char: i
Char pos: 2, Char:  
Char pos: 3, Char: 世
Char pos: 6, Char: 界

必须为您阅读博客 post:

The Go Blog: Strings, bytes, runes and characters in Go


注意:如果您必须遍历 string 字节 (而不是它的字符),请使用 for ... range 和转换后的 string 就像你的第二个例子没有复制,它被优化掉了。有关详细信息,请参阅

Which one of the following methods are better performance-wise?

绝对不是这个。

str := "large text"
str2 := []byte(str)
for _, s := range str2 {
  // use s
}

字符串是不可变的。 []byte 是可变的。这意味着 []byte(str) 复制了一份。所以上面将复制整个字符串。我发现不知道何时复制字符串是大字符串性能问题的主要来源。

如果 str2 从未改变,编译器 可能 优化副本。因此,最好像这样编写上面的代码,以确保永远不会更改字节数组。

str := "large text"
for _, s := range []byte(str) {
  // use s
}

这样就不会 str2 以后可能会被修改并破坏优化。

但这是个坏主意,因为它会破坏任何多字节字符。见下文。


至于 byte/rune 转换,性能不是考虑因素,因为它们不等价。 c 将是一个符文,str[i] 将是一个字节。如果您的字符串包含多字节字符,则必须使用符文。

例如...

package main

import(
    "fmt"
)

func main() {
    str := "snow ☃ man"
    for i, c := range str {
        fmt.Printf("c:%c str[i]:%c\n", c, str[i])
    }
}

$ go run ~/tmp/test.go
c:s str[i]:s
c:n str[i]:n
c:o str[i]:o
c:w str[i]:w
c:  str[i]: 
c:☃ str[i]:â
c:  str[i]: 
c:m str[i]:m
c:a str[i]:a
c:n str[i]:n

请注意,使用 str[i] 会破坏多字节 Unicode 雪人,它只包含多字节字符的第一个字节。

无论如何都没有性能差异,因为 range str 已经必须逐个字符地进行工作,而不是逐字节地进行。