「よーするに Unicode ってなんなのさ!」ということでいろいろ調べてみたものをまとめてみました。で、頑張って調べてはみたのですが調べれば調べるほどイモヅル式に新しい情報が出てきてまとめきりませんでした。なので少々不完全です。いちおう「Unicode ってどんなものなのかな~」ってことが分かってもらえればと思います。
このコーナーでは、「45」のように二桁で区切られた文字を16進で表された1バイトの表現として扱います。このコーナーではこのような表現もビット列と呼ぶことにします。例えば「F0」という16進のバイト表現があれば「11110000」のようなビット列を思い浮かべてください。
コンピュータが扱えるデータはそもそも「0」と「1」の2進数です。2進数は頑張れば10進数とか16進数とかになるので数値は使えます。ですが本来、文字は扱えません。文字は「数値」ではなく「絵」だからです。ではコンピュータはどうやって文字を扱っているのでしょうか?
実は文字(絵)を数値に対応させて、対応する文字(絵)を画面に表示しているにすぎないのです。この文字と数値を対応付けた規則が文字コードと呼ばれるものです。以下に例を示します。
ASCII
1 <--> 31
= <--> 3D
A <--> 41
a <--> 61
* 全て16進
この ASCII コードはコンピュータが世に出回り始めたころに定められたもので、英語圏だけを想定していました。ですので、対応付けは主にアルファベットと数字、よく使われる記号だけで、これらを表現するには 128文字 = 7ビット あれば十分でした。ただ、コンピュータにとっては7ビットよりも 8ビット=1バイト のほうが扱いやすいので、ASCII コードは1バイト文字として普及しました。
さて、コンピュータが世界中に普及すると言語の問題が徐々に大きくなってきました。例えば、漢字等を表現するには到底1バイトでは足りないからです。そこで考え出されたのが、漢字等を2バイトで表現する方法です。日本では Windows 系で有名な「Shift-JIS」や UNIX 系の「EUC」があります。以下に例を示します
Shift-JISの例
横浜city
横 <--> 89 A1
浜 <--> 95 6C
c <--> 63
i <--> 69
t <--> 74
y <--> 79
89 A1 95 6C 63 69 74 79 というビット列になっている。
このような方法は他の言語でも使われていたため、それらとの重複がおこり、一つのファイル中に複数の言語が存在できない状態になってしまいました。これを解決するために、世界中の文字を一つの対応付け規則にまとめようということになりました。この規則が Unicode です。
基本的に Unicode は全ての文字が2バイトで表現できるように対応付けが行われました。また、ASCII コードの部分はそのまま ASCII コードと同じになっています。以下に例を示します。
横浜city
横 <--> 6A2A
浜 <--> 6D5C
c <--> 63
i <--> 69
t <--> 74
y <--> 79
Unicode とは、このように文字と数値の対応付けなのですが、どのようにビット列表現するかはまた別の問題になってきます。例えば、「c=63」を「63」という1バイトで表すのか、「00 63」という2バイトで表すのか、といった違いです。このように、文字コードを実際のビット列でどのように表現するのかを定めたものを「文字エンコーディング」といいます。
UTF-8 は Unicode をビット列にするエンコーディングの一つです。その方法ですが、UTF-8 では、ASCII コードの部分をそのまま1バイトで表現します。で、あとは2バイトで表現します・・・、と行きたいところですが次のような場合を考えてください。
52 63
UTF-8 ではこのようなあいまいさを解決するために、実際の Unicode に付加情報をあたえたビット列として文字を扱います。そのため、UTF-8 では一つの文字が1バイトから3バイトで表現されます。
その付加情報を与える方法ですが、とりあえず.を見てください。Unicode の範囲のうち、2進で「1」をとりうる部分が2進表現で xyz となっています。この xyz の部分のうち、x の部分が1バイト目に、y の部分が2バイト目に、z の部分が3バイト目にそれぞれ付加情報を与えられた上で分割されます。そして、赤字の部分が付加されたビットです。
| Unicode 範囲 | 2進表現 | 付加情報 付与後 | ||
| 1バイト目 | 2バイト目 | 3バイト目 | ||
| 開始 0000 終了 007F |
0000 0000 0xxx xxxx | 0xxx xxxx | ||
| 開始 0080 終了 07FF |
0000 0xxx xxyy yyyy | 110x xxxx | 10yy yyyy | |
| 開始 0800 終了 FFFF |
xxxx yyyy yyzz zzzz | 1110 xxxx | 10yy yyyy | 10zz zzzz |
例えば
52 63 の2進表現は 0101 0010 0110 0011 また、52 63 は 08 00 < 52 63 < FF FF
| x | y | z |
| 0101 | 00 1001 | 10 0011 |
| x | y | z |
| 1110 0101 | 1000 1001 | 1010 0011 |
| 1バイト目 | 2バイト目 | 3バイト目 |
こうすることによりコンピュータはまず1バイト読み取り、最初のビットが「0」ならばその1バイトを一つの文字として認識し、最初の3ビットが「110」ならば次のビットとあわせて一つの文字と認識します。同様に、最初の4ビットが「1110」ならば続く2ビットとあわせて一つの文字と認識することができるわけです。
UTF-16 は UTF-8 より簡単です。基本的には Unicode を全て2バイトで表現したものだからです。以下に例を示します。
UTF-16 ビット列表現
横浜city
横 <--> 6A 2A
浜 <--> 6D 5C
c <--> 00 63
i <--> 00 69
t <--> 00 74
y <--> 00 79
しかし、実際にはエンディアンという概念が問題になってきます。
エンディアンとは多バイトで表現されるデータをどういう順序で並べるか、ということです。並べ方といっても下位バイト->上位バイトの並び順か上位バイト->下位バイトの並び順しかありません。前者を「リトル・エンディアン(little endian)」と呼び、後者を「ビッグ・エンディアン(big endian)」と呼びます。この説明だけでは分かりにくいと思うので、次の例を見てください。
横 <-->6A 2A (Unicode) リトル・エンディアン -> 下位バイト・上位バイト = 2A 6A ビッグ・エンディアン -> 上位バイト・下位バイト = 6A 2A
UTF-16 ではビッグ、リトル両方のエンディアンを認めています。ではファイルなどから読み込む場合はどのように判断するのでしょう。UTF-16 でエンコーディングされたテキストファイルなどのデータには、先頭に BOM(Byte Order Mark)と呼ばれる2バイトの付加情報がつけられています(プログラミングなどでファイルに書き込むときもこの情報を自分で付加しなければなりません)。この最初の2バイトが「FF FE」ならリトル・エンディアン、「FE FF」ならビッグエンディアンと判断します。以下にまとめます。
FF FE -> リトル・エンディアン FE FF -> ビッグ・エンディアン
話が前後してしまいますが、UTF-8 にも BOM が存在します。UTF-8 はもともと Unicode をエンコーディングしたものなので一種類しかないのですが、UTF-16 にあわせて識別子のような意味合いで BOM をつけることができます。で、UTF-8 の BOM は
EF BB BF
BOMあり UTF-8:UTF-8 BOM無し UTF-8:UTF-8N
当初 Unicode は全ての文字を2バイトの領域に収めるつもりでしたが、それでは収まりきりませんでした。そこで、Unicode の一部分は4バイトで表現されます。この範囲は「010000~1FFFFF」です。もちろん、これに対応した UTF-8 や UTF-16 のエンコーディング方法があります。まずは UTF-8 です。先ほど示した表と同様の表.を用意しましたので、そちらをご覧ください。
| Unicode 範囲 | 2進表現 | 付加情報 付与後 | |||
| 1バイト目 | 2バイト目 | 3バイト目 | 4バイト目 | ||
| 開始 0000 終了 007F |
0000 0000 0xxx xxxx | 0xxx xxxx | |||
| 開始 0080 終了 07FF |
0000 0xxx xxyy yyyy | 110x xxxx | 10yy yyyy | ||
| 開始 0800 終了 FFFF |
xxxx yyyy yyzz zzzz | 1110 xxxx | 10yy yyyy | 10zz zzzz | |
| 開始 010000 終了 1FFFFF |
0000 0000 000x xxyy yyyy zzzz zzww wwww |
1111 0xxx | 10yy yyyy | 10zz zzzz | 10ww wwww |
次に UTF-16 ですが、UTF-16 ではこの4バイトの文字を2バイトの組み合わせで表現する方法が定められています。その際、UTF-8 の説明でしたような「2バイトで1文字なのか4バイトで一文字なのか」といった重複の問題を避けるために Unicode の2バイト部分には何も対応付けがなされていない専用の領域が用意されて、これをサロゲート領域と呼びます。どういうことかというと、Unicode で「D800~DFFF」の範囲(これがサロゲート領域)にあるビット列がでてきたら、次の2バイトと合わせて1文字と認識しなさい、と定められています(逆に言うと、「D800~DFFF」はそれだけで表現される文字というのが定められていません)。そしてこの領域のうち「D800~DBFF」は4バイトのうち上位2バイトで使い、「DC00~DFFF」は下位2バイトで使われます。以下にまとめます。
D800~DBFF 上位サロゲート(4バイト文字の上位2バイト) DC00~DFFF 下位サロゲート(4バイト文字の下位2バイト)
一つ例を書いてみます。
D9 45 DE 38
で、実際の4バイト部分(「010000~1FFFFF」)の割り当てがサロゲートではどう表現されるのかという問題ですが、これは次のようになっています。
010000 -> D800 DC00 010001 -> D800 DC01 : 0103FF -> D800 DFFF 010400 -> D801 DC00 010401 -> D801 DC01 : 0107FF -> D801 DFFF : : 10FC00 -> DBFF DC00 10FC01 -> DBFF DC01 : 10FFFF -> DBFF DFFF
これらのエンコーディングに対して、UTF-32 というものがあります。UTF-32 は32ビット、つまり4バイトで Unicode を表現します。ですので、特殊な変換なしに Unicode の対応付けがそのままビット列表現になっています。とはいっても、エンディアンのビッグかリトルかは気を付けなければなりません。また、エンディアンを判定するための BOM も4バイトになっています。以下に BOM を示します。
FF FE 00 00 -> リトル・エンディアン 00 00 FE FF -> ビッグ・エンディアン