Unicodeを1〜4バイトの可変長で符号化する、最も普及した文字エンコーディング
UTF-8=Unicode Transformation Format-8bit の略で、「Unicode(世界中の文字に番号を振った規格)」の文字番号を、実際のバイト列に変換する方式のひとつです。Unicodeはあくまで「あ=U+3042 番」のように番号を決めるだけの仕様で、その番号をどんなバイトの並びで保存・送信するかを決めるのがUTF-8です。
身近な例で言うと、住所を郵便で送るときの「封筒への書き方ルール」に近いです。住所(=Unicode番号)そのものは決まっていても、それを実際に紙にどう書いて運ぶか(=バイト列)にはルールが必要です。UTF-8はその「運び方のルール」の中で世界で最も広く使われているもので、Web(HTML)やほとんどのプログラムの標準になっています。
上のツールで文字を入力すると、その文字がUTF-8で何バイトになり、各バイトがどんなビットで構成されるかが表示されます。A・あ・😀のサンプルを切り替えて、バイト数が文字によって変わる様子を見てみてください。
UTF-8の特徴は「文字によって使うバイト数が変わる(可変長)」ことです。よく使う文字は少ないバイト数で、めったに使わない文字は多いバイト数で表します。これにより、英語の文章ならファイルサイズが小さく済みます。
| バイト数 | コードポイント範囲 | ビットパターン | 例 |
|---|---|---|---|
| 1 | U+0000〜007F | 0xxxxxxx | A, 1, ! |
| 2 | U+0080〜07FF | 110xxxxx 10xxxxxx | ñ, é |
| 3 | U+0800〜FFFF | 1110xxxx 10xxxxxx 10xxxxxx | あ, 漢 |
| 4 | U+10000〜10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 😀 |
バイト数を見分ける仕掛けは「先頭ビットのパターン」です。上のツールでも色分けして表示しています。
・先頭バイトの最初の1の個数=そのバイト数を意味する(110なら2バイト、1110なら3バイト)
・後続バイトは必ず 10 で始まる(文字の途中だと一目で分かる)
・残りの x の部分に、文字番号のビットを詰める
この仕組みのおかげでどこから読んでも文字の区切りが分かるのが大きな利点です。先頭が 0 なら1バイト文字、10 なら文字の途中、11 なら新しい文字の先頭、と機械的に判断できます。
後続バイト(2バイト目以降)が必ず 10 で始まる理由は、「バイト列のどの位置から読み始めても、今読んでいるバイトが文字の先頭か途中かを一瞬で判定できる」ようにするためです。
ルールを整理すると以下の3種類だけです。
・先頭ビットが 0:1バイト文字(ASCII領域)の先頭
・先頭2ビットが 11:2〜4バイト文字の先頭バイト(1の個数でバイト数がわかる)
・先頭2ビットが 10:後続バイト(文字の途中)
これを自己同期性(=どこから読んでも文字の区切りが分かる性質)と呼びます。たとえばファイルの途中から読み込んだとき、まず先頭ビットが 10 のバイトを読み飛ばし続ければ、必ず次の文字の先頭(0 または 11)に到達できます。この仕組みにより、通信エラーで数バイト欠けても次の文字から正常に復帰できるのです。
| 方式 | 特徴 | ASCII互換 | よく使う場面 |
|---|---|---|---|
| UTF-8 | 可変長(1〜4バイト) | ○ | Web・Linux・macOS |
| Shift_JIS | 可変長(1〜2バイト) | △(一部NG) | 古いWindowsソフト・CSV |
| UTF-16 | 固定2 or 4バイト | × | Windows内部・Java |
| EUC-JP | 可変長(1〜3バイト) | ○ | 旧UNIX・メール |
文字化けとは、ファイルを書いたときの文字コードと、読み込んだときの文字コードが違うために発生する「解釈の食い違い」です。バイト列そのものは正しく保存されているのに、違うルールで解読しようとするから文字が崩れます。
身近なたとえで言うと、モールス信号を送ったのに相手がアルファベットと聞き間違えたのと同じです。信号(バイト列)は正確に届いていても、解読ルール(文字コード)が違えば意味が変わります。
代表的な文字化けのパターンを2つ紹介します。
・Shift_JISで書いたファイルをUTF-8として読む:日本語の箇所が「縺ゅ&縺薙″縺ゅ▲縺ヲ」のような全く違う文字になる。逆もまた然り
・Shift_JISで「¥」記号がバックスラッシュになる:Shift_JISでは 0x5C が「¥」だが、ASCIIでは「\」。同じバイトなのに解釈が違う(完全なASCII互換でない例)
UTF-8が現在の標準になった理由は、ASCII互換・自己同期・世界中のすべての文字を一つの規格で扱えるという3つの利点を兼ね備えているからです。Webのページの宣言(<meta charset="UTF-8">)はこの文字コードをブラウザに伝えるためのもので、宣言がないと文字化けの原因になります。
UTF-8がここまで普及した最大の理由が「ASCIIと完全な互換性がある」ことです。コードポイントが U+0000〜U+007F(=ASCIIの128文字)の場合、UTF-8では1バイトで表現され、その値はASCIIコードとまったく同じになります。
例えば「A」はASCIIで 0x41(10進65)。UTF-8でも先頭ビットが 0 の1バイト 01000001 = 0x41 です。つまり英数字だけの文章なら、ASCIIファイルとUTF-8ファイルは中身が1バイトも変わりません。
この互換性により、次のような利点が生まれます。
・既存のASCIIデータをそのままUTF-8として扱える(変換不要)
・英数字中心の通信・保存ではサイズが増えない(1文字1バイトのまま)
・日本語など多バイト文字の中にASCII記号が混ざってもズレない(後続バイトは必ず 10 始まりでASCII領域と被らない)
UTF-8の主な性質は「ASCIIと上位互換」「ASCII文字は1バイト」「日本語は3バイトが基本」です。固定長で全文字を同じバイト数にするUTF-16/32との違い(UTF-8は可変長)も合わせて理解しておくとよいでしょう。