将[]byte
解码成[]rune
真的很容易(简单地转换成string
,然后转换成[]rune
效果很好,我假设它默认为utf8,并带有无效字符的填充字节).我的问题是——你打算如何将[]rune
解码回utf8格式的[]byte
?
我是不是遗漏了什么,或者我的[]rune
中的每个符文都需要手动呼叫EncodeRune?当然有一个编码器,我可以简单地把Writer
传给它.
将[]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上try ):
Hello 世界
Hello 世界
Go Specification: Conversions人明确提到了这个 case :Conversions to and from a string type,第3点:
将符号片转换为字符串类型会生成一个字符串,该字符串是转换为字符串的各个符号值的串联.
请注意,上面的解决方案-尽管可能是最简单的-可能不是最有效的.原因是它首先创建一个string
值,该值将保存UTF-8编码形式的符文的"副本",然后它将字符串的支持片复制到结果字节片(必须进行复制,因为string
值是不可变的,并且如果结果片将与string
共享数据,我们将能够修改string
的内容;有关详细信息,请参见golang: []byte(string) vs []byte(*string)和Immutable string and pointer address).
请注意,智能编译器可以检测到无法引用中间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()
函数.这样做的好处是,我们不会"浪费"内存,也不必进行最后的切片(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
正如怀疑的那样,第二个版本速度更快,第三个版本速度最快,尽管性能yield 不是很大.通常情况下,首选第一个最简单的解决方案,但如果这是在应用程序的某个关键部分(并且执行了很多次),那么使用第三个版本可能是值得的.