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

String 以字节形式访问字符串元素是否执行转换?,string,go,type-conversion,rune,String,Go,Type Conversion,Rune,在Go中,要访问字符串的元素,我们可以编写: str := "text" for i, c := range str { // str[i] is of type byte // c is of type rune } 访问str[i]时,Go是否执行从rune到byte的转换?我想答案是肯定的,但我不确定。如果是这样的话,那么以下哪种方法的性能更好?一种方法是否优于另一种方法(例如,在最佳实践方面) 或 Go中的string值存储文本的UTF-8编码字节,而不是其字符或runes s

在Go中,要访问
字符串的元素
,我们可以编写:

str := "text"
for i, c := range str {
  // str[i] is of type byte
  // c is of type rune
}
访问
str[i]
时,Go是否执行从
rune
byte
的转换?我想答案是肯定的,但我不确定。如果是这样的话,那么以下哪种方法的性能更好?一种方法是否优于另一种方法(例如,在最佳实践方面)


Go中的
string
值存储文本的UTF-8编码字节,而不是其字符或
rune
s

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

当您将
用于。。。范围
字符串
上,它在
字符串
符文
上迭代,而不是其字节

因此,如果您想迭代
符文
(字符),您必须使用
来。。。范围
,但不转换为
[]字节
,因为第一种形式不适用于包含多(UTF-8)字节字符的
字符串
值。 要为…执行
。。。范围
字符串
值上,第一次迭代值将是当前字符的字节索引,第二次迭代值将是
rune
类型的当前字符值(它是
int32
的别名):

对于字符串值,“range”子句迭代字符串中从字节索引0开始的Unicode代码点。在连续迭代中,索引值将是字符串中连续UTF-8编码代码点的第一个字节的索引,第二个rune类型的值将是相应代码点的值。如果迭代遇到无效的UTF-8序列,第二个值将是0xFFFD,Unicode替换字符,下一次迭代将在字符串中前进一个字节

简单的例子:

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

必须阅读您的博客文章:


注意:如果必须迭代
字符串的字节(而不是其字符),请使用
作为。。。范围
与转换的
字符串
类似,您的第二个示例没有复制,它已优化。有关详细信息,请参阅

以下哪种方法的性能更好

绝对不是这个

str := "large text"
str2 := []byte(str)
for _, s := range str2 {
  // use s
}
字符串是不可变的<代码>[]字节
是可变的。这意味着
[]字节(str)
会复制一份。因此,上面的代码将复制整个字符串。我发现不知道何时复制字符串是大型字符串性能问题的主要来源

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

str := "large text"
for _, s := range []byte(str) {
  // use s
}
这样以后就不会有
str2
可能被修改并破坏优化

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


至于字节/符文转换,性能不是一个考虑因素,因为它们不是等价的
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
必须一个字符接一个字符,而不是一个字节接一个字节,所以性能没有差别。

oops,我把它倒过来了关于[]字节/字符串的第一部分本身是不正确的。编译器在[]字节/字符串周围执行转义分析和一些额外的优化,以避免某些分配。如果用“-gcflags”-m-m”编译第一个代码段,您将看到这种情况不会分配,因为str2只在循环中使用,不会转义。@nussjustin是的。将其作为
写入。。。范围[]字节(str)
使优化更为明显。通常,将
字符串
转换为
[]字节
会生成副本,但如果在
中用于。。。范围
,则不会。编译器对它进行了优化。详情见此。
str := "large text"
str2 := []byte(str)
for _, s := range str2 {
  // use s
}
str := "large text"
for _, s := range []byte(str) {
  // use s
}
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