Unicodeと文字コードの仕組み ― なぜ文字化けは起きるのか


UTF-8 の可変長エンコーディング 文字 コードポイント バイト列 サイズ A ASCII U+0041 0 1000001 1byte 日本語 U+3042 1110 0011 10 000001 10 000010 3byte 😀 絵文字 U+1F600 11110 000 10 011111 10 011000 10 000000 4byte 先頭ビット規則: 0xxxxxxx = 1バイト 1110xxxx = 3バイト 11110xxx = 4バイト Webでのエンコーディング使用率 UTF-8 98%
UTF-8は文字の種類に応じて1〜4バイトを使い分ける
ひよこ ひよこ

メールとかWebページで「文字化け」ってたまに見るけど、あれってなんで起きるの?

ペンギン先生 ペンギン先生

いい質問だね。コンピュータは文字を「数字」として扱っているんだけど、昔はその数字の割り当て方が国や地域ごとにバラバラだったんだよ。英語圏ではASCIIっていう規格で、A=65、B=66みたいに128文字だけ定義されていた。日本語はShift_JIS、中国語はGB2312っていう別々の規格を使っていたんだ。

ひよこ ひよこ

それぞれ別のルールで数字を割り振ってたってこと? そりゃ混ざったらおかしくなるよね…

ペンギン先生 ペンギン先生

そのとおり。日本語のShift_JISで書かれたデータを、中国語のGB2312として読もうとすると、まったく違う文字に見えてしまう。これが文字化けの正体だよ。で、この問題を解決するために生まれたのがUnicodeなんだ。

ひよこ ひよこ

Unicodeってどういう仕組みなの?

ペンギン先生 ペンギン先生

Unicodeは「世界中の文字に一つずつ番号を振ろう」という壮大なプロジェクトだよ。たとえばA はU+0041、「あ」はU+3042っていうコードポイントが割り当てられている。現在15万文字以上が登録されていて、絵文字や古代文字まで含まれているんだ。

ひよこ ひよこ

でも番号を決めただけだと、実際にファイルに保存するときはどうするの?

ペンギン先生 ペンギン先生

そこで登場するのがUTF-8っていうエンコーディングだよ。UTF-8はコードポイントを1〜4バイトの可変長で表現する仕組みなんだ。英語のアルファベットはASCIIと同じ1バイト、日本語のひらがなは3バイト絵文字は4バイトっていう具合にね。ASCII互換性があるから、英語のテキストはそのまま読めるのが大きな強みだよ。

ひよこ ひよこ

だからWebサイトのほとんどがUTF-8を使ってるんだね! UTF-16とかUTF-32っていうのもあるって聞いたけど…

ペンギン先生 ペンギン先生

UTF-16はJavaScript文字列Windowsの内部APIで使われているよ。基本的な文字は2バイトで表現するんだけど、U+FFFFを超える文字――たとえば一部の漢字や絵文字――は「サロゲートペア」っていう2つの16ビット値の組み合わせで表現する必要があるんだ。UTF-32はすべて4バイト固定だからシンプルだけど、メモリ効率が悪くてあまり使われないね。

ひよこ ひよこ

サロゲートペアって何だか難しそう…具体的にはどういうこと?

ペンギン先生 ペンギン先生

UTF-16では0xD800〜0xDBFFが「上位サロゲート」、0xDC00〜0xDFFFが「下位サロゲート」として予約されていて、この2つをペアにして1つの文字を表すんだ。たとえば絵文字の「😀」はU+1F600だけど、UTF-16では0xD83D+0xDE00という2つの値のペアになる。JavaScriptで'😀'.lengthが2になるのはこのせいだよ。

ひよこ ひよこ

えっ、絵文字1個なのにlengthが2になるの!? じゃあ絵文字ってもっと複雑な仕組みもあるの?

ペンギン先生 ペンギン先生

あるよ! たとえば肌の色を変えるスキントーンモディファイアや、ZWJ(ゼロ幅接合子)シーケンスっていう仕組みがあるんだ。家族の絵文字「👨‍👩‍👧‍👦」は、実は👨+ZWJ+👩+ZWJ+👧+ZWJ+👦っていう7つのコードポイントを結合して1つの絵文字として表示しているんだよ。だから見た目は1文字なのに内部的にはかなり長いんだ。

ひよこ ひよこ

すごい…! 他にもUnicodeで意外と厄介な問題ってあるの?

ペンギン先生 ペンギン先生

正規化」っていう問題があるよ。たとえば「café」(eの後に結合アクセント記号)と「café」(最初から合成済みのé)は、見た目は同じ「café」だけどバイト列が違うんだ。NFCという正規化形式で合成済みに統一するか、NFDで分解形に統一しないと、文字列の比較やファイル名の一致判定で問題が起きるよ。macOSのファイルシステムはNFDを使うから、Windowsで作ったファイル名と一致しないことがあるんだ。

ひよこ ひよこ

プログラマーが気をつけるべきポイントって他にもある?

ペンギン先生 ペンギン先生

ベテランでも引っかかる有名な問題があるよ。「トルコ語のI問題」っていって、英語ではiの大文字はIだけど、トルコ語ではiの大文字はİ(上にドット付き)、Iの小文字はı(ドットなし)なんだ。だからtoUpperCase()をロケール考慮せずに使うと、トルコ語環境でバグが出る。もう一つ怖いのがホモグラフ攻撃。キリル文字のа(U+0430)とラテン文字のa(U+0061)は見た目がほぼ同じだから、偽のURLを作ってフィッシングに使われることがあるんだ。

ひよこ ひよこ

見た目が同じなのに中身が違う文字があるなんて怖い…! Unicodeって奥が深いんだね。

ペンギン先生 ペンギン先生

そうだね。Unicodeのおかげで世界中の文字を統一的に扱えるようになったけど、その分だけ複雑さも増しているんだ。プログラミングでは「文字列の長さ=見た目の文字数とは限らない」「大文字変換はロケールに依存する」「同じに見える文字が別のコードポイントかもしれない」っていう3つを意識しておくと、文字まわりのバグをかなり防げるよ。