Unicode と UTF

サイト全体の目次

稿


稿

Unicode ってなに?

「よーするに Unicode ってなんなのさ!」ということでいろいろ調べてみたものをまとめてみました。で、頑張って調べてはみたのですが調べれば調べるほどイモヅル式に新しい情報が出てきてまとめきりませんでした。なので少々不完全です。いちおう「Unicode ってどんなものなのかな~」ってことが分かってもらえればと思います。

このコーナーでは、「45」のように二桁で区切られた文字を16進で表された1バイトの表現として扱います。このコーナーではこのような表現もビット列と呼ぶことにします。例えば「F0」という16進のバイト表現があれば「11110000」のようなビット列を思い浮かべてください。

コンピュータと文字

コンピュータが扱えるデータはそもそも「0」と「1」の2進数です。2進数は頑張れば10進数とか16進数とかになるので数値は使えます。ですが本来、文字は扱えません。文字は「数値」ではなく「絵」だからです。ではコンピュータはどうやって文字を扱っているのでしょうか?

実は文字(絵)を数値に対応させて、対応する文字(絵)を画面に表示しているにすぎないのです。この文字と数値を対応付けた規則が文字コードと呼ばれるものです。以下に例を示します。

ASCII
1 <--> 31
= <--> 3D
A <--> 41
a <--> 61
    * 全て16進
		
ここで示したものは「ASCII」コードと呼ばれる対応付けです。「1」が数値ではなく数字である場合もこの対応付けが決められています。

この 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

基本的に Unicode は全ての文字が2バイトで表現できるように対応付けが行われました。また、ASCII コードの部分はそのまま ASCII コードと同じになっています。以下に例を示します。

横浜city
    横 <--> 6A2A
    浜 <--> 6D5C
    c <--> 63
    i <--> 69
    t <--> 74
    y <--> 79
		
また、Unicode 対応付け一覧を示したページ.も参考になるでしょう。

http://www.unicode.org/charts/

Unicode とは、このように文字と数値の対応付けなのですが、どのようにビット列表現するかはまた別の問題になってきます。例えば、「c=63」を「63」という1バイトで表すのか、「00 63」という2バイトで表すのか、といった違いです。このように、文字コードを実際のビット列でどのように表現するのかを定めたものを「文字エンコーディング」といいます。

UTF-8

UTF-8 は Unicode をビット列にするエンコーディングの一つです。その方法ですが、UTF-8 では、ASCII コードの部分をそのまま1バイトで表現します。で、あとは2バイトで表現します・・・、と行きたいところですが次のような場合を考えてください。

52 63
		
この場合、「52」と「63」を分けて考えれば「Rc」という ASCII 文字になりますし、「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
			

の範囲にあるので3バイトに分割されます。まずこの2進表現を
xyz
010100 100110 0011

に分割します。これに赤字のビットを付加します。
xyz
1110 01011000 10011010 0011
1バイト目2バイト目3バイト目

これが「52 63」の UTF-8 形式のビット列表現です。ちなみに、これを16進表示すると「E5 89 A3」となります。

こうすることによりコンピュータはまず1バイト読み取り、最初のビットが「0」ならばその1バイトを一つの文字として認識し、最初の3ビットが「110」ならば次のビットとあわせて一つの文字と認識します。同様に、最初の4ビットが「1110」ならば続く2ビットとあわせて一つの文字と認識することができるわけです。

UTF-16

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
		
このように ASCII 部分も2バイトで表現します。ですから、コンピュータは常に2バイトずつ読み込んで、それを1文字として認識します。

しかし、実際にはエンディアンという概念が問題になってきます。

エンディアン

エンディアンとは多バイトで表現されるデータをどういう順序で並べるか、ということです。並べ方といっても下位バイト->上位バイトの並び順か上位バイト->下位バイトの並び順しかありません。前者を「リトル・エンディアン(little endian)」と呼び、後者を「ビッグ・エンディアン(big endian)」と呼びます。この説明だけでは分かりにくいと思うので、次の例を見てください。

横 <-->6A 2A (Unicode)

リトル・エンディアン -> 下位バイト・上位バイト = 2A 6A
ビッグ・エンディアン -> 上位バイト・下位バイト = 6A 2A
			
このように、ビッグ・エンディアンの場合はバイト(ビット列)の並び順をそのまま並べればもとのデータに復元できますが、リトル・エンディアンの場合は対象のバイト(ビット列)を読み込んだ時点で並び順を逆にしてやらなければいけません。

UTF-16 のエンディアンと BOM

UTF-16 ではビッグ、リトル両方のエンディアンを認めています。ではファイルなどから読み込む場合はどのように判断するのでしょう。UTF-16 でエンコーディングされたテキストファイルなどのデータには、先頭に BOM(Byte Order Mark)と呼ばれる2バイトの付加情報がつけられています(プログラミングなどでファイルに書き込むときもこの情報を自分で付加しなければなりません)。この最初の2バイトが「FF FE」ならリトル・エンディアン、「FE FF」ならビッグエンディアンと判断します。以下にまとめます。

FF FE -> リトル・エンディアン
FE FF -> ビッグ・エンディアン
			
なお、一般に Unicode といえば UTF-16 のリトル・エンディアンを指すようです。

UTF-8 の BOM

話が前後してしまいますが、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
			
といって区別することもあります。

サロゲート領域と UTF-32

当初 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
		
通常、「D9 45」で一文字として認識しますが、これはサロゲート領域にあるので対応する文字がありません。代わりに続く2バイト「DE 38」も読み込んで「D9 45 DE 38」の4バイトで一文字と認識するわけです。「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
		
見てのとおり、Unicode に対応して下位のサロゲートから上位のサロゲートへと繰り上げしていっているだけです。もちろん、エンディアンに関しては通常の領域同様に注意が必要です。

これらのエンコーディングに対して、UTF-32 というものがあります。UTF-32 は32ビット、つまり4バイトで Unicode を表現します。ですので、特殊な変換なしに Unicode の対応付けがそのままビット列表現になっています。とはいっても、エンディアンのビッグかリトルかは気を付けなければなりません。また、エンディアンを判定するための BOM も4バイトになっています。以下に BOM を示します。

FF FE 00 00 -> リトル・エンディアン
00 00 FE FF -> ビッグ・エンディアン