如何在golang中使用utf8将[]符号编码为[]字节?

时间:2015-03-25 12:31:17

标签: string go unicode utf-8 type-conversion

所以很容易将[]byte解码为[]rune(简单地转换为string,然后转换为[]rune效果非常好,我假设默认值to utf8和forvalids的填充字节)。我的问题是 - 您如何以utf8格式将此[]rune解码回[]byte

我错过了某些内容,还是为[]rune中的每一个符文手动调用了EncodeRune?当然有一个编码器,我可以简单地将Writer传递给。

1 个答案:

答案 0 :(得分:30)

您只需将符文切片([]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明确提及此案例:Conversions to and from a string type,第3点:

  

将一片符文转换为字符串类型会产生一个字符串,该字符串是转换为字符串的各个符文值的串联。

请注意,上述解决方案 - 虽然可能是最简单的 - 可能不是最有效的。原因是它首先创建一个string值,该值将以UTF-8编码形式保存符文的“副本”,然后将字符串的后备切片复制到结果字节切片(副本具有由于string值是不可变的,并且如果结果切片与string共享数据,我们将能够修改string的内容;有关详细信息,请参阅{ {3}}和golang: []byte(string) vs []byte(*string))。

请注意,智能编译器可以检测到无法引用中间string值,从而消除其中一个副本。

我们可以通过分配单个字节切片来获得更好的性能,并将符文逐个编码到其中。我们已经完成了。为了方便起见,我们可以致电Immutable string and pointer address包来帮助我们:

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))

以上的输出是一样的。在unicode/utf8上尝试。

请注意,为了创建结果切片,我们必须猜测结果切片的大小。我们使用了最大估计,即符文数乘以符文可编码的最大字节数(utf8.UTFMax)。在大多数情况下,这将比需要的更大。

我们可能会创建第三个版本,我们首先计算所需的确切大小。为此,我们可以使用Go Playground函数。获得的好处是我们不会“浪费”记忆,我们也不必进行最后的切片(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

正如所怀疑的那样,第二个版本更快,第三个版本最快,但性能提升并不大。一般来说,首选最简单的解决方案是首选,但如果这是您应用程序的某些关键部分(并且执行了很多次),则第三个版本可能值得使用。