テキストとバイナリ:ASCII、UTF-8、文字とバイトの関係

約7分

「テキスト」は人間が読む文字、「バイナリ」はコンピュータが扱うバイト列。両者の橋渡しが文字エンコーディングです。本記事では 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進
A650x4101000001
Z900x5A01011010
a970x6101100001
0480x3000110000
(空白)320x2000100000
\n (改行)100x0A00001010

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+007F1 バイト0xxxxxxx
U+0080〜U+07FF2 バイト110xxxxx 10xxxxxx
U+0800〜U+FFFF3 バイト1110xxxx 10xxxxxx 10xxxxxx
U+10000〜U+10FFFF4 バイト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 進などのバイナリ表現に変換したい場面では、本サイトのテキスト⇔バイナリ変換ツールが使えます。