たのしい駆動開発

たのしいアウトプットの場所

UnicodeとUTF-8と、GoのByte型とrune型についてのまとめ

UTF-8,Unicode, Goのbyte, rune関係がよく分からなかったのでいろいろ調べて、自分なりに解釈ができたので、まとめてみようと思います。



まずは定義から行きましょう。
UTF-8: Unicodeで使える8ビット符号単位の文字符号化形式
Unicode: 文字集合(文字セット)が単一の大規模文字セット

ようするに、UTF-8は、Unicodeを符号化(エンコード)するやつで、Unicodeはいろんな文字の集合です。
aとか"あ"とか"亜"とか、ほんといろいろな文字の集合。

そして、Unicodeの文字には、識別しやすいように数字が割り振られていて、その数字をコードポイント(Go言語でrune型に割り当てられる)といいます。実際にコードで違いを見てみましょう。

func main() {
    s := "あいうえお"
    b := []byte(s)
    for _, bi := range b {
        fmt.Printf("%x ", bi)   //UTF-8でエンコードされた、16進数のバイト列
    }
    fmt.Println()
    for _, ri := range s {
        fmt.Printf("%#U", ri)  //コードポイント(rune)
    }
}


実行結果

e3 81 82 e3 81 84 e3 81 86 e3 81 88 e3 81 8a 
U+3042 'あ'U+3044 'い'U+3046 'う'U+3048 'え'U+304A 'お'



"あいうえお"などのUnicodeを、UTF-8という変換器を使ってPC側が処理できるようにしたものがe3~~などのバイト列です。



UTF-8は可変長エンコーディングで、1~4バイトです。
各バイトの先頭は、ビットパターンが決まっています。
1バイト 0.......
2バイト 110..... 10......
3バイト 1110.... 10...... 10......
4バイト 111110... 10...... 10...... 10......

ASCIIと互換性を持たせるために、UTF-8の1バイトのみはASCIIになっています。

"あ"の16進数のバイト列は、e3 81 82です。
これを2進数に直すと、1110 0011 1000 0001 1000 0010 であり、各バイト先頭はきちんとビットパターン通りになっています。




このバイト列は、Goではbyte型として扱われます。

s := "あ"
b := []byte(s)
for _, bi := range b {
  fmt.Printf("%x ", bi)  //e3 81 82
}

文字列をbyteスライスに変換して16進で取り出すと"e3 81 82"となり、byte型がUTF-8エンコードされたバイト列だということが分かりました。



おまけ

func main() {
    s := "あいうえお"
    fmt.Printf("%v, %T\n", s[0:6], s[0:6])  //あい, string
}

とやると、"あい"が出てきます。これは、スライスで取り出そうとすると、byteで取得してるからこうなってる訳ですね。それをデコードして、string型にしています。


参考記事

ja.wikipedia.org

text.baldanders.info

qiita.com

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)