ASCII符号体系(1バイト構成、標準ASCIIはUS-ASCIIまたは7ビットASCIIとも呼ばれ128文字、拡張ASCIIは256文字)
C言語を学習した際、コンピュータ内部のメカニズムについていくつか理解しました。すべての情報が最終的にバイナリ文字列として表現されることを知り、各ビットが0と1の2つの状態を持つことで、異なる組み合わせによって世界中のあらゆるものを表現できると気づきました。これは中国の「太極」に似た感覚です。「太極が両儀を生み、両儀が四象を生み、四象が八卦を生む」。
コンピュータでは、1バイトが8ビットのバイナリ数に対応し、各ビットは0、1の2つの状態を持つため、1バイトで256の状態を組み合わせることができます。この256の状態のそれぞれが1つの記号に対応すれば、1バイトのデータで256文字を表現できます。そこでアメリカ人は、英語の文字とこの8ビットバイナリ数の対応関係を記述した符号化(辞書のようなもの)を制定しました。これがASCII符号体系です。
ASCII符号体系は合計128文字を定義しており、例えば大文字の'A'は65(10進数で、2進数では0100 0001)です。この128文字は8ビットバイナリ数の後ろの7ビットのみを使用し、最上位ビットはすべて0に規定されています。
歴史的な問題 英語の文字を128文字で符号化するだけで十分でしたが、他の言語を表現するには128文字では不十分でした。そこで、いくつかのヨーロッパの国々は、ASCII符号体系で未使用の最上位ビットを活用することに決め、これにより256文字を表現できるようになりました。しかし、ここで別の問題が発生しました。異なる国々の文字セットが異なる可能性があり、256文字すべてを表現できたとしても、同じコードポイント(8ビットバイナリ数)が表す文字が異なる場合があるということです。例えば、144はアラブ人のASCII符号体系ではگですが、ロシアのASCII符号体系ではђです。
したがって、ASCII符号体系の問題は、すべての人が0-127番目の文字については合意しているものの、128-255番目の文字については多くの異なる解釈があるという点です。同時に、アジア言語にはより多くの文字を保存する必要があり、1バイトでは不十分になりました。そこで、人々は文字を保存するために2バイトを使用し始めました。
様々な符号化方式はシステム開発者の悪夢となりました。なぜなら、彼らはソフトウェアを国外に販売したかったからです。そこで彼らは「コードページ」の概念を提案し、対応言語のコードページに切り替えることで、対応言語の文字を表示できるようにしました。この場合、多言語を使用する場合は、頻繁にコードページを切り替える必要がありました。
Unicode(各文字のバイナリコードのみを規定し、保存方法は規定しない)
最終的に、アメリカ人は世界中のすべての言語のすべての文字を表示するための標準的なソリューションを提案すべきだと気づきました。この目的のために、Unicodeが誕生しました。
Unicodeは非常に厚い辞書であり、世界中のすべての文字に対応する数字を記録しています。具体的な対応関係や、どのように分割されているかは私たちが考慮する問題ではありません。Unicodeがすべての文字にその文字を表すための数字を指定したというだけです。
Unicodeについていくつかの誤解があります。Unicodeは単なる文字集合であり、文字に対応するバイナリコードを規定するだけで、そのバイナリコードをどのように保存するかについては一切規定していません。その考え方は非常にシンプルで、各文字にその文字を表すための数字を指定するだけです。
Unicode符号化方式 前に述べたように、Unicodeは文字に対応するバイナリコードをどのように保存するかを規定していません。例えば、漢字「漢」の場合、そのUnicodeコードポイントは0x6c49で、対応する2進数は110110001001001です。この2進数は15ビットあり、少なくとも2バイトで表現する必要があることを示しています。想像できるように、Unicode辞書の後の文字では、3バイトや4バイト、あるいはより多くのバイトが必要になるかもしれません。
これはいくつかの問題を引き起こします。コンピュータは、この2バイトが1つの文字を表しているのか、それとも2つの文字をそれぞれ表しているのかをどうやって知るのでしょうか?ここで考えられるのは、最大値を取ることです。仮にUnicodeで最大の文字が4バイトで表現できるとすれば、すべての文字を4バイトで表現し、足りない場合は前に0を補うという方法です。確かに、この方法で符号化の問題を解決できますが、スペースの極端な浪費を引き起こします。英語のドキュメントの場合、ファイルサイズは3倍大きくなり、これは明らかに受け入れられません。
したがって、Unicodeの符号化問題をより良く解決するために、現在広く使用されているUTF-8とUTF-16の2つの符号化方式が誕生しました。もちろん、UTF-32という符号化方式もあります。これは先述のような固定長符号化で、文字を一律で4バイト使用します。便利に見えますが、他の2つの符号化方式ほど広く使用されていません。
UTF-8(Unicodeの保存方式の一つ、1から4バイトで文字を保存)
UTF-8は非常に印象的な符号化方式であり、ASCII符号体系との後方互換性を美しく実現することで、Unicodeが受け入れられることを保証しました。
UTF-8は現在インターネットで最も広く使用されているUnicode符号化方式であり、その最大の特徴は可変長であることです。1から4バイトを使用して文字を表現し、文字によって長さが変化します。符号化ルールは以下の通りです:
単一バイトの文字については、最初のビットを0に設定し、残りの7ビットをこの文字のUnicodeコードポイントに対応させます。したがって、英語の0-127番目の文字はASCII符号体系と完全に同じです。これはASCII符号体系時代のドキュメントをUTF-8符号化で開いても全く問題がないことを意味します。
Nバイト(N > 1)で表現する必要がある文字については、最初のバイトの最初のNビットをすべて1に設定し、N+1番目のビットを0に設定します。残りのN-1バイトの最初の2ビットはすべて10に設定し、残りのビット位置はこの文字のUnicodeコードポイントで埋めます。
符号化ルールは以下の通りです:
Unicode16進コードポイント範囲 UTF-8 2進数
0000 0000 - 0000 007F 0xxxxxxx
0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
上記の符号化ルール对照表に基づいて、UTF-8符号化と復号化は非常に簡単になります。以下に漢字「漢」を例に、具体的にUTF-8符号化と復号化の方法を説明します。
「漢」のUnicodeコードポイントは0x6c49(110 1100 0100 1001)です。上記の对照表から、0x0000 6c49が3行目の範囲にあることがわかります。したがって、その形式は1110xxxx 10xxxxxx 10xxxxxxとなります。次に、「漢」の2進数の最後のビットから始めて、対応する形式のxに順番に埋めていきます。不足するxは0で補います。これにより、「漢」のUTF-8符号化は11100110 10110001 10001001となり、16進数に変換すると0xE6 0xB7 0x89となります。
復号化のプロセスも非常に簡単です:バイトの最初のビットが0の場合、このバイトは1文字に対応します。バイトの最初のビットが1の場合、連続する1の数が文字が占めるバイト数を示します。
UTF-16(Unicodeの保存方式の一つ、UCS-2とも呼ばれ、2または4バイトで文字を保存。基本平面の文字は2バイト、補助平面の文字は4バイトを使用)
UTF-16符号化方式を理解する前に、もう一つの概念「平面」について理解しましょう。
上記の紹介で、Unicodeは非常に厚い辞書であり、世界中のすべての文字を1つの集合で定義していると述べました。これだけ多くの文字は一度に定義されたわけではなく、領域ごとに定義されています。各領域は65536個(2^16)の文字を格納でき、これを「平面」と呼びます。現在、合計17個(2^5)の平面があり、つまりUnicode文字集合全体のサイズは現在2^21です。
最も前の65536文字の位置は「基本平面」(BMPと略称)と呼ばれ、そのコードポイントの範囲は0から2^16-1、つまり16進数でU+0000からU+FFFFです。すべての最も一般的な文字はこの平面に配置されており、これはUnicodeで最初に定義および公開された平面です。残りの文字はすべて「補助平面」(SMPと略称)に配置され、コードポイントの範囲はU+010000からU+10FFFFです。
平面の概念を基本的に理解した後、UTF-16に戻りましょう。UTF-16符号化はUTF-32とUTF-8の間にあり、同時に固定長と可変長の2つの符号化方法の特徴を組み合わせています。その符号化ルールは非常にシンプルです:基本平面の文字は2バイトを使用し、補助平面の文字は4バイトを使用します。つまり、UTF-16の符号化長は2バイト(U+0000からU+FFFF)か4バイト(U+010000からU+10FFFF)のいずれかです。では、2バイトに遭遇した場合、この2バイトを1文字として扱うのか、それとも後の2バイトと合わせて1文字として扱うのでしょうか?
ここに非常に巧妙な点があります。基本平面内では、U+D800からU+DFFFが空のセグメントであり、これらのコードポイントはどの文字にも対応していません。したがって、この空のセグメントは補助平面の文字をマッピングするために使用できます。
補助平面の文字位置は合計2^20個あり、したがってこれらの文字を表現するには少なくとも20ビットが必要です。UTF-16はこの20ビットを2つに分割し、前の10ビットをU+D800からU+DBFFにマッピング(これを高位(H)と呼ぶ)、後の10ビットをU+DC00からU+DFFFにマッピング(これを低位(L)と呼ぶ)します。これは、補助平面の文字が2つの基本平面の文字として表現されることを意味します。
したがって、2バイトに遭遇し、そのコードポイントがU+D800からU+DBFFの間にある場合、続く2バイトのコードポイントがU+DC00からU+DFFFの間にあると判断でき、この4バイトは一緒に解釈する必要があります。
次に、漢字"𠮷"を例に、UTF-16符号化方式がどのように機能するかを説明します。
漢字"𠮷"のUnicodeコードポイントは0x20BB7であり、これは明らかに基本平面の範囲(0x0000 - 0xFFFF)を超えているため、4バイトで表現する必要があります。まず0x20BB7 - 0x10000を計算して超過部分を求め、それを20ビットで表現します(不足する場合は前に0を補う)、結果は0001000010 1110110111となります。次に、前の10ビットをU+D800からU+DBFFの間にマッピングし、後の10ビットをU+DC00からU+DFFFにマッピングします。U+D800に対応する2進数は1101100000000000であり、直接後の10ビットを埋め込むと1101100001000010となり、16進数に変換すると0xD842となります。同様に、低位は0xDFB7となります。したがって、漢字"𠮷"のUTF-16符号化は0xD842 0xDFB7となります。
Unicode3.0では、補助平面文字の変換式が以下のように与えられています:
高位 = Math.floor((コードポイント - 0x10000) / 0x400) + 0xD800
低位 = (コードポイント - 0x10000) % 0x400 + 0xDC00
この符号化式に基づいて、文字のUTF-16符号化を簡単に計算できます。
UTF-32(Unicodeの保存方式の一つ、固定長の4バイトで文字を保存、UCS-4とも呼ばれる)
UTF-32の符号化方式では、すべての文字を4バイトで表現し、足りない場合は前に0を補います。確かに、この方法で符号化の問題を解決できますが、スペースの極端な浪費を引き起こします。英語のドキュメントの場合、ファイルサイズは3倍大きくなります。
UTF-32の符号化方式、つまり先述のような固定長符号化は、文字を一律で4バイト使用します。便利に見えますが、他の2つの符号化方式ほど広く使用されていません。