Python
UTF-8

UTF-8ってそもそもどういう仕組み?


はじめに

UTF-8がどういう仕組みか、曖昧なままだったので調べてみました。


文字符号化方式と符号化文字集合

この2つは全く別物で、

符号化文字集合はユニコードなどの、ある文字に対してある数字やコードが当てられたリストのようなもの

文字符号化方式はUTF-8やshift-jisなどの、ビット列と符号化文字集合とを橋渡しをするものです。

アルファベットや数字のみを扱う時にはASCIIだけ事足りるので、この2つはの違いが意識しづらいようです。


UTF-8の規則


①ビット列の先頭のパターン

1バイトの文字や4バイトの文字が混在していても大丈夫なように、UTF-8ではそれぞれのバイトの先頭のビット列が決まったパターンをとるようにしています。


1バイトの文字

先頭を0とする。


2バイトの文字

1バイト目の先頭を110、2バイト目の先頭を10とする。


3バイトの文字

1バイト目の先頭を1110、2バイト目と3バイト目の先頭を10とする。

文字の始まりを探そうと思えば、先頭が10でないバイト列を探せばいいわけです。


②先頭より後

前頭よりも後の部分は有効ビットと呼ばれ、文字を特定する役割があります。

例えば、「あ」の実態は

111000111000000110000010

ですが、先頭のビットパターンを除くと、

0011000001000010

が現れます。

この有効ビットを16進数にすると、そのままユニコードの符号位置(U+○○○○)となります。


UTF-8でエンコードした例

「オロナミンC」をPythonでエンコードしてみます。

>>> 'オロナミンC'.encode()

b'\xe3\x82\xaa\xe3\x83\xad\xe3\x83\x8a\xe3\x83\x9f\xe3\x83\xb3C'

Pythonのバイト型では、16進数の前に\xと付くようです。最後の「C」はそのままです。

ですから、「オロナミンC」をUTF-8でエンコードすると、実際には16進数で

E382AAE383ADE3838AE3839FE383B343

となると思います。(最後の「C」は1バイトの文字で、UTF-8ではASCIIと同じ43となります。)

2進数(ビット列)に直してみます

11100011100000101010101011100011100000111010110111100011100000111000101011100011100000111001111111100011100000111011001101000011

これを8ビット毎に区切っていくと、先頭のビットパターンの規則により、文字の区切れがわかります。

すると、最初の文字を表す二進数は111000111000001010101010だとわかります。

ここから有効ビットの部分を取り出すと、0011000010101010です。

そして16進数に戻すと、30AAとなり、これは「オ」のユニコード U+30AA に対応しています。


まとめ

UTF-8でエンコードしたビット列は16進数で表記されることが多いです。

そこから文字を読み取るには、2進数に変換して文字の区切れを見つけ、有効ビットを抽出する必要があるようです。