什么决定了循环 UTF-8 字符串时字符的位置?

Hen*_*yTK 2 string unicode for-loop utf-8 go

我正在阅读Effective Go 文档中关于for语句的部分,并遇到了这个例子:

for pos, char := range "??\x80?" {
    fmt.Printf("Character %#U, at position: %d\n", char, pos)
}
Run Code Online (Sandbox Code Playgroud)

输出是:

Character U+65E5 '?', at position: 0
Character U+672C '?', at position: 3
Character U+FFFD '?', at position: 6
Character U+8A9E '?', at position: 7
Run Code Online (Sandbox Code Playgroud)

我不明白的是为什么位置是 0、3、6 和 7。这告诉我第一个和第二个字符长 3 个字节,“替换符文”(U + FFFD)长 1 个字节,我接受和理解。但是,我认为runeint32类型,因此每个是 4 个字节,而不是 3 个。

为什么某个范围内的位置与每个值应该消耗的内存总量不同?

icz*_*cza 6

string在Go值存储为只读字节切片([]byte),其中,字节是(的UTF-8编码的字节rune的多个)stringUTF-8是一种变长编码,不同的 Unicode 码位可能使用不同的字节数进行编码。例如,范围内的值0..127被编码为单个字节(其值是 unicode 代码点本身),但大于 127 的值使用超过 1 个字节。该unicode/utf8包包含与 UTF-8 相关的实用程序函数和常量,例如utf8.UTFMax报告有效 Unicode 代码点在 UTF-8 编码中可能“占用”的最大字节数(即 4)。

这里要注意一件事:并非所有可能的字节序列都是有效的UTF-8 序列。Astring可以是任何字节序列,即使是那些无效的 UTF-8 序列。例如,该string"\xff"表示无效的 UTF-8 字节序列,有关详细信息,请参阅如何在 Go 中表示可选字符串?

所述for range构建体-当施加在string以上的符文值-迭代string

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

for range构造可能会产生 1 或 2 个迭代值。使用 2 时,就像在您的示例中一样:

for pos, char := range "??\x80?" {
    fmt.Printf("Character %#U, at position: %d\n", char, pos)
}
Run Code Online (Sandbox Code Playgroud)

对于每次迭代,pos将是符文/字符的字节索引,char并将是string. 正如你在上面的引用中看到的,如果string是一个无效的 UTF-8 字节序列,当遇到一个无效的 UTF-8 序列时,char将是0xFFFD(Unicode 替换字符),并且for range构造(迭代)将推进一个单一的仅字节

总结一下:位置总是rune当前迭代的字节索引(或者更具体地说:当前迭代的UTF-8编码序列的第一个字节的字节索引rune),但如果无效UTF-遇到8个序列,位置(索引)在下一次迭代中只会增加1。

如果您想了解有关该主题的更多信息,则必须阅读博客文章:

Go 博客:Go 中的字符串、字节、符文和字符