テキストとバイナリ:ASCII、UTF-8、文字とバイトの関係
「テキスト」は人間が読む文字、「バイナリ」はコンピュータが扱うバイト列。両者の橋渡しが文字エンコーディングです。本記事では ASCII から UTF-8 までの仕組みと、実装で遭遇する罠を整理します。
ASCII:7 bit で英語を表す
ASCII(American Standard Code for Information Interchange、1963)は、英字・数字・記号を 7 bit(128 値)で表す規格:
- 0-31:制御文字(タブ、改行、ESC など)
- 32:スペース
- 33-126:印刷可能文字(記号、数字、英大小)
- 127:DEL
主要な値:
| 文字 | 10進 | 16進 | 2進 |
|---|---|---|---|
A | 65 | 0x41 | 01000001 |
Z | 90 | 0x5A | 01011010 |
a | 97 | 0x61 | 01100001 |
0 | 48 | 0x30 | 00110000 |
(空白) | 32 | 0x20 | 00100000 |
\n (改行) | 10 | 0x0A | 00001010 |
7 bit なので 1 文字あたり 1 バイト(最上位ビットは 0)。
ASCII の限界:英語以外が表現できない
128 値では:
- 日本語、中国語、ハングルなど東アジアの文字は不可
- ヨーロッパの一部の文字(ç、ü、ñ など)も不可
- 絵文字や特殊記号も不可
これを解決するために、世界で 数十種類のエンコーディングが乱立した時代がありました:
- ISO-8859-1(西欧)
- Shift_JIS、EUC-JP(日本語)
- GB2312、Big5(中国語)
- KOI8-R(ロシア語)
異なるエンコーディングのテキストを混ぜると文字化けする「文字エンコーディング地獄」が発生。
Unicode:すべての文字に番号を振る
Unicode は世界のすべての文字を統一的に番号(コードポイント)で識別する規格:
- 最大 0x10FFFF(約 110 万)
- 各文字に唯一のコードポイント
- 言語に依存しない
例:
A= U+0041あ= U+3042🍎= U+1F34E
ただし、Unicode は「番号を割り当てる」だけで、バイトとしてどう保存するかは別問題。これがエンコーディングの役割。
UTF-8:可変長エンコーディング
UTF-8 は Unicode を 1〜4 バイトの可変長で表すエンコーディングで、現代の事実上の標準:
| コードポイント範囲 | UTF-8 バイト数 | パターン |
|---|---|---|
| U+0000〜U+007F | 1 バイト | 0xxxxxxx |
| U+0080〜U+07FF | 2 バイト | 110xxxxx 10xxxxxx |
| U+0800〜U+FFFF | 3 バイト | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000〜U+10FFFF | 4 バイト | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
特性:
- ASCII 互換:U+0000〜U+007F は 1 バイトで ASCII と同じ
- 自己同期:途中から読み始めても文字境界が分かる
- 多言語対応:すべての Unicode 文字を表せる
例:A = 01000001(1 バイト)、あ = 11100011 10000001 10000010(3 バイト)。
UTF-16 と UTF-32
UTF-8 以外のエンコーディング:
- UTF-16:1 文字を 2 または 4 バイト。Windows、Java、JavaScript の内部表現
- UTF-32:1 文字を常に 4 バイト。シンプルだが容量が大きい
UTF-16 では BMP(基本多言語面)外の文字(絵文字など)は サロゲートペアで 4 バイト。これが JavaScript の '🍎'.length === 2 の原因。
バイトオーダーマーク(BOM)
UTF-8/16/32 のファイル先頭に付く 3〜4 バイトのマーカー:
- UTF-8 BOM:
EF BB BF(オプショナル) - UTF-16 LE:
FF FE - UTF-16 BE:
FE FF
UTF-8 では本来 BOM 不要ですが、Windows のメモ帳などは付けることがあり、Linux のシェルスクリプト先頭に BOM があると #!/bin/bash が認識されないなどのトラブルが起きます。
文字エンコーディング判定の難しさ
ファイルのエンコーディングはバイト列だけからは確定できない:
- ASCII テキスト → ASCII、UTF-8、Shift_JIS、ISO-8859-1 のどれでも有効
- 日本語ファイル → UTF-8 か Shift_JIS かはバイトパターンで推測
ライブラリ(chardet、icu)は統計的に推測しますが 100% 正確ではない。メタデータで明示するのが安全:
- HTML:
<meta charset="utf-8"> - HTTP:
Content-Type: text/html; charset=utf-8 - ファイル:拡張子規約や BOM
実装で頻出する罠
1. 文字数とバイト数の混同
'あ'.length; // 1(文字数)
new Blob(['あ']).size; // 3(UTF-8 バイト数) データベースのカラム長制限が「バイト数」「文字数」のどちらかでズレる。
2. 結合文字
が は か(U+304B)+ ゛(U+3099)の 2 コードポイントで表せる場合と、が(U+304C)の 1 コードポイントで表せる場合がある。同じ文字に見えても比較で false になる。
正規化が必要:
'が'.normalize('NFC'); // 'が' (U+304C)に正規化 3. UTF-8 の不正バイト
UTF-8 として無効なバイト列(孤立したサロゲートなど)を含むデータを扱うとエラーになる。データソースが信頼できない場合は事前バリデーション必要。
4. 古いシステムの ASCII 限定
メールヘッダ(RFC 5321)や URL 構造(RFC 3986)は本来 ASCII のみ。日本語を入れる場合は MIME エンコードや Punycode が必要。
まとめ
- ASCII は 7 bit、英語のみ
- Unicode はすべての文字に番号を振る規格
- UTF-8 は可変長で ASCII 互換、現代の標準
- 文字数とバイト数は別、エンコーディング判定は確定不能
- メタデータでエンコーディングを明示するのが安全
テキストを 2 進・8 進・16 進などのバイナリ表現に変換したい場面では、本サイトのテキスト⇔バイナリ変換ツールが使えます。