byte 和 rune

byte 是 uint8 的别名,其字面量是 8 位整数值,byte 切片相比于不可变的 string 方便常用许多。它可以更改每个字节或字符。这对于处理文件内容(无论是文本文件、二进制文件还是来自网络的 I/O 流)非常有效。byte 切片是一个可变的字节序列

rune 是 int32 的别名,其字面量是 32 位整数值,用来表示 Unicode 字符编码。rune 类似于 byte,不同点在于 rune 每个索引是一个字符而不是一个字节。rune 切片是对字节片的重新分组使得每个索引都是一个字符。

如果你处理的文本文件有很多非 ascii 字符,比如中文文本、数学公式或带有表情符号的文本,使用 rune 是最好的。

rune 也是从字符串中获取子字符串的理想选择。它支持 Unicode 字符,没有数据损坏的风险。

对于 []rune,len()和索引都是基于 rune(int32)的。 当你将 []rune 转换为 string 时,每个 rune 成为字符串中的一个 utf-8 字符。

byte 和 rune 的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
s := "GÖ"
sample := "H哈"

sByte := []byte(s)
sRune := []rune(s)
sampleByte := []byte(sample)
sampleRune := []rune(sample)

fmt.Printf("%s\nsByte: %d\nsRune: %d\n", s, sByte, sRune)
fmt.Println("------")
fmt.Printf("%s\nsampleByte: %d\nsampleRune: %d\n", sample, sampleByte, sampleRune)
}

可以看到 rune 中非 ASCII 码字符的 Unicode 编码为 1-3 字节,与 ASCII 码字符的字节数不一定相同。

string

string 是不可变的 byte 切片。因为 Go 中的源代码使用 utf-8 编码,因此每个字符串也使用 utf-8 编码,即 string 字面量是 utf-8 编码,以 byte 为单位的。string 中的每个字符实际占用 1-3 个字节,而每个 rune 占 4 个字节。

  • 使用 rune() 可把 byte 为单位的字符转换为 rune 字符,对于 ASCII 字符来说,rune 值和 byte 值相同,而对于 Unicode 编码的字符来说便不同了。
  • 使用 []rune() 将 string 转换 rune 数组。当将字符串转换为 rune 切片时,字符串中的每个 utf-8 字符被转换为一个 rune,从而获得包含字符串 Unicode 编码字符的新切片。
  • 对于 string,len()和索引都是基于 byte(unint8)的
  • 在 Go 中将字符串转换为 rune 切片是一个标准操作,没有数据损坏的风险。 字符串是处理短字节或字符序列比较好的方法。每次对字符串进行操作(例如查找替换字符串或接受子字符串)时,都会创建一个新的字符串。如果字符串非常大,例如文件内容,则效率非常低。

string 中索引字符

例如我们先看下面程序:

1
2
3
4
5
6
7
8
9
10
11
func main() {

sample := "Hel哈"
for i := 0; i < len(sample); i++ {
fmt.Print(sample[i], " ")
}
fmt.Println()
fmt.Printf("%s\t的字节长度: %d\n", sample, len(sample))
fmt.Printf("哈\t字符的字节长度: %d\n", utf8.RuneLen('哈'))
fmt.Printf("%s\t的字符长度: %d\n", sample, utf8.RuneCountInString(sample))
}

输出:

1
2
3
4
72 101 108 229 147 136
Hel哈 的字节长度: 6
哈 字符的字节长度: 3
Hel哈 的字符长度: 4

可以看出字符串的长度(len)和字符串的字符长度(RuneCountInString)是不同的。因为字符串是字节切片,Go 默认 UTF-8,存储非 ASCII 字符时,每个字符则会存储 1-3 个字节。所以想要正确地索引字符串中的字符有以下两种方法:

  1. 将字符串转换为 rune 切片
  2. 使用 range 操作符迭代 Unicode 字符 也就是说对字符串进行 rang 迭代,是字符迭代,而不是字节迭代。

关于编码的一些方法

utf8.ValidRune(chr): 判断 chr 是否可以编码为合法的 utf-8 序列。 utf8.RuneLen(chr): 查看字符 chr 的字节长度 utf8.RuneCount(): 查看字节数组中按照 rune 单位的字符长度 utf8.RuneCountInString: 查看字符串按照 rune 单位的长度

参考资料

  1. Golang: How To Convert String To Rune in Go Example
  2. go - What is a rune? - Stack Overflow