如何使用 utf8 将 []rune 编码为 []byte
How encode []rune into []byte using utf8
将 []byte
解码为 []rune
非常容易(只需转换为 string
,然后转换为 []rune
效果非常好,我假设它默认为 utf8 并带有用于无效的填充字节)。我的问题是 - 您打算如何将此 []rune
解码回 utf8 格式的 []byte
?
我是不是遗漏了什么,或者我是否为 []rune
中的每个符文手动调用了 EncodeRune?当然有一个编码器,我可以简单地将 Writer
传递给它。
您可以简单地将符文切片 ([]rune
) 转换为 string
,然后再将其转换回 []byte
。
示例:
rs := []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}
bs := []byte(string(rs))
fmt.Printf("%s\n", bs)
fmt.Println(string(bs))
输出(在 Go Playground 上尝试):
Hello 世界
Hello 世界
Go Specification: Conversions mentions this case explicitly: Conversions to and from a string type,第 3 点:
Converting a slice of runes to a string type yields a string that is the concatenation of the individual rune values converted to strings.
请注意,上述解决方案 - 尽管可能是最简单的 - 可能不是最有效的。原因是因为它首先创建一个 string
值,该值将保存 UTF-8 编码形式的 "copy" 符文,然后它将字符串的支持切片复制到结果字节切片(a必须进行复制,因为string
值是不可变的,如果结果切片与string
共享数据,我们就可以修改string
的内容;详情,参见 and )。
请注意,智能编译器可以检测到无法引用中间 string
值,从而消除其中一个副本。
我们可以通过分配单个字节切片并将符文一个一个地编码到其中来获得更好的性能。我们完成了。为了轻松做到这一点,我们可以调用 unicode/utf8
包来帮助我们:
rs := []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}
bs := make([]byte, len(rs)*utf8.UTFMax)
count := 0
for _, r := range rs {
count += utf8.EncodeRune(bs[count:], r)
}
bs = bs[:count]
fmt.Printf("%s\n", bs)
fmt.Println(string(bs))
上面的输出是一样的。在 Go Playground.
上试试
请注意,为了创建结果切片,我们必须猜测结果切片有多大。我们使用了最大估计值,即符文数乘以一个符文可以编码的最大字节数 (utf8.UTFMax
)。在大多数情况下,这会比需要的大。
我们可能会创建第三个版本,我们首先计算所需的确切大小。为此,我们可以使用 utf8.RuneLen()
函数。这样做的好处是我们不会 "waste" 内存,也不必进行最终切片 (bs = bs[:count]
)。
让我们比较一下性能。要比较的 3 个函数(3 个版本):
func runesToUTF8(rs []rune) []byte {
return []byte(string(rs))
}
func runesToUTF8Manual(rs []rune) []byte {
bs := make([]byte, len(rs)*utf8.UTFMax)
count := 0
for _, r := range rs {
count += utf8.EncodeRune(bs[count:], r)
}
return bs[:count]
}
func runesToUTF8Manual2(rs []rune) []byte {
size := 0
for _, r := range rs {
size += utf8.RuneLen(r)
}
bs := make([]byte, size)
count := 0
for _, r := range rs {
count += utf8.EncodeRune(bs[count:], r)
}
return bs
}
以及基准测试代码:
var rs = []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}
func BenchmarkFirst(b *testing.B) {
for i := 0; i < b.N; i++ {
runesToUTF8(rs)
}
}
func BenchmarkSecond(b *testing.B) {
for i := 0; i < b.N; i++ {
runesToUTF8Manual(rs)
}
}
func BenchmarkThird(b *testing.B) {
for i := 0; i < b.N; i++ {
runesToUTF8Manual2(rs)
}
}
结果:
BenchmarkFirst-4 20000000 95.8 ns/op
BenchmarkSecond-4 20000000 84.4 ns/op
BenchmarkThird-4 20000000 81.2 ns/op
正如所怀疑的那样,第二个版本更快,第三个版本最快,尽管性能提升并不大。一般来说,首选第一个最简单的解决方案,但如果这是您应用程序的某个关键部分(并且执行了很多次),则可能值得使用第三个版本。
将 []byte
解码为 []rune
非常容易(只需转换为 string
,然后转换为 []rune
效果非常好,我假设它默认为 utf8 并带有用于无效的填充字节)。我的问题是 - 您打算如何将此 []rune
解码回 utf8 格式的 []byte
?
我是不是遗漏了什么,或者我是否为 []rune
中的每个符文手动调用了 EncodeRune?当然有一个编码器,我可以简单地将 Writer
传递给它。
您可以简单地将符文切片 ([]rune
) 转换为 string
,然后再将其转换回 []byte
。
示例:
rs := []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}
bs := []byte(string(rs))
fmt.Printf("%s\n", bs)
fmt.Println(string(bs))
输出(在 Go Playground 上尝试):
Hello 世界
Hello 世界
Go Specification: Conversions mentions this case explicitly: Conversions to and from a string type,第 3 点:
Converting a slice of runes to a string type yields a string that is the concatenation of the individual rune values converted to strings.
请注意,上述解决方案 - 尽管可能是最简单的 - 可能不是最有效的。原因是因为它首先创建一个 string
值,该值将保存 UTF-8 编码形式的 "copy" 符文,然后它将字符串的支持切片复制到结果字节切片(a必须进行复制,因为string
值是不可变的,如果结果切片与string
共享数据,我们就可以修改string
的内容;详情,参见
请注意,智能编译器可以检测到无法引用中间 string
值,从而消除其中一个副本。
我们可以通过分配单个字节切片并将符文一个一个地编码到其中来获得更好的性能。我们完成了。为了轻松做到这一点,我们可以调用 unicode/utf8
包来帮助我们:
rs := []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}
bs := make([]byte, len(rs)*utf8.UTFMax)
count := 0
for _, r := range rs {
count += utf8.EncodeRune(bs[count:], r)
}
bs = bs[:count]
fmt.Printf("%s\n", bs)
fmt.Println(string(bs))
上面的输出是一样的。在 Go Playground.
上试试请注意,为了创建结果切片,我们必须猜测结果切片有多大。我们使用了最大估计值,即符文数乘以一个符文可以编码的最大字节数 (utf8.UTFMax
)。在大多数情况下,这会比需要的大。
我们可能会创建第三个版本,我们首先计算所需的确切大小。为此,我们可以使用 utf8.RuneLen()
函数。这样做的好处是我们不会 "waste" 内存,也不必进行最终切片 (bs = bs[:count]
)。
让我们比较一下性能。要比较的 3 个函数(3 个版本):
func runesToUTF8(rs []rune) []byte {
return []byte(string(rs))
}
func runesToUTF8Manual(rs []rune) []byte {
bs := make([]byte, len(rs)*utf8.UTFMax)
count := 0
for _, r := range rs {
count += utf8.EncodeRune(bs[count:], r)
}
return bs[:count]
}
func runesToUTF8Manual2(rs []rune) []byte {
size := 0
for _, r := range rs {
size += utf8.RuneLen(r)
}
bs := make([]byte, size)
count := 0
for _, r := range rs {
count += utf8.EncodeRune(bs[count:], r)
}
return bs
}
以及基准测试代码:
var rs = []rune{'H', 'e', 'l', 'l', 'o', ' ', '世', '界'}
func BenchmarkFirst(b *testing.B) {
for i := 0; i < b.N; i++ {
runesToUTF8(rs)
}
}
func BenchmarkSecond(b *testing.B) {
for i := 0; i < b.N; i++ {
runesToUTF8Manual(rs)
}
}
func BenchmarkThird(b *testing.B) {
for i := 0; i < b.N; i++ {
runesToUTF8Manual2(rs)
}
}
结果:
BenchmarkFirst-4 20000000 95.8 ns/op
BenchmarkSecond-4 20000000 84.4 ns/op
BenchmarkThird-4 20000000 81.2 ns/op
正如所怀疑的那样,第二个版本更快,第三个版本最快,尽管性能提升并不大。一般来说,首选第一个最简单的解决方案,但如果这是您应用程序的某个关键部分(并且执行了很多次),则可能值得使用第三个版本。