web-dev-qa-db-ja.com

golangでutf8を使用して[] runeを[] byteにエンコードする方法は?

したがって、[]byte[]runeにデコードするのは本当に簡単です(単にstringにキャストしてから[]runeにキャストすると、非常にうまく機能します。デフォルトでutf8になっていると想定しています。無効のフィラーバイトを含む)。私の質問は-[]rune[]byteにutf8形式でデコードして戻すにはどうすればよいですか?

何かが足りないのですか、または[]runeのすべてのルーンに対して手動で EncodeRune を呼び出していますか?確かに、単純にWriterを渡すことができるエンコーダーがあります。

17
Herp Derpington

ルーンスライス(_[]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仕様:変換 はこのケースについて明示的に言及しています: 文字列型との間の変換 、ポイント#3:

ルーンのスライスを文字列型に変換すると、文字列に変換された個々のルーン値を連結した文字列が生成されます。

上記の解決策が最も簡単な場合もありますが、最も効率的ではない可能性があることに注意してください。そしてその理由は、最初にルーンの「コピー」をUTF-8エンコード形式で保持するstring値を作成し、次に文字列のバッキングスライスを結果のバイトスライス(コピーstring値は不変であり、結果スライスがstringとデータを共有する場合、stringの内容を変更できるため、作成する必要があります。詳細については、 golang:[] byte(string)vs [] byte(* string) および Immutable string and pointer address )を参照してください。

スマートコンパイラは、中間のstring値を参照できないことを検出し、コピーの1つを削除する可能性があることに注意してください。

シングルバイトスライスを割り当てて、ルーン文字を1つずつエンコードすることにより、パフォーマンスを向上させることができます。これで完了です。これを簡単に行うために、 _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_)。ほとんどの場合、これは必要以上に大きくなります。

最初に必要な正確なサイズを計算する3番目のバージョンを作成する場合があります。これには、 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
_

疑いがあるように、パフォーマンスの向上はそれほど大きくありませんが、2番目のバージョンの方が速く、3番目のバージョンが最も高速です。一般に、最初の最も簡単なソリューションが推奨されますが、これがアプリの重要な部分にある場合(そして何度も実行される場合)、3番目のバージョンを使用する価値があります。

36
icza