したがって、[]byte
を[]rune
にデコードするのは本当に簡単です(単にstring
にキャストしてから[]rune
にキャストすると、非常にうまく機能します。デフォルトでutf8になっていると想定しています。無効のフィラーバイトを含む)。私の質問は-[]rune
を[]byte
にutf8形式でデコードして戻すにはどうすればよいですか?
何かが足りないのですか、または[]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仕様:変換 はこのケースについて明示的に言及しています: 文字列型との間の変換 、ポイント#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番目のバージョンを使用する価値があります。