文字コード
Character Encoding ・ もじコード
文字と数値の対応、そして番号をバイト列にする規則。書く規則と読む規則がずれると文字化けになる。
✦ 30秒で試す — 航海ExcelでCSVを開くと化けるのに、メモ帳なら読める化けて表示されたとき、ファイルのバイトの列は?予想してみる概要
文字コードは、文字と数値の対応規則です。コンピュータが直接扱えるのは2進数のビット列だけなので、「A は 65、あ は 12354」のように文字に番号を割り振り、その番号をバイト列として記録する — この約束事全体を文字コード(文字エンコーディング)と呼びます。
普段は言語やフレームワークが自動で処理してくれるため意識せずに済みますが、ひとたび約束がすれ違うと「譁�ュ怜喧縺�」のような文字化けとなって表面化します。CSVを Excel で開いたら日本語が崩れた、メールの件名が読めない、絵文字を保存したらデータベースがエラーを吐いた — これらはすべて文字コードの語彙で説明できる現象です。テキストを扱わないシステムはほぼ存在しない以上、すべてのエンジニアにとっての基礎教養といえます。
なぜ生まれたか
出発点は1963年の ASCII です。英数字と記号あわせて128文字に0〜127の番号を割り振った7ビットの規格で、英語圏ではこれで十分でした。しかし世界には英語以外の文字があります。各国は「ASCIIの空き領域や拡張領域に自国の文字を詰め込む」独自規格を作り始め、日本だけでも Shift_JIS、EUC-JP、ISO-2022-JP が併存し、欧州には Latin-1 系の亜種が乱立しました。同じバイト列でも規格が違えば別の文字に読める — 文字化けはこの乱立の必然的な帰結です。さらに各規格は互いに変換しづらく、多言語が混ざった文書(日本語と韓国語を1ファイルに)はそもそも表現できませんでした。
この分断を解決するために生まれたのが Unicode です。「世界中のすべての文字に、ただ一つの番号体系で番号を振る」という統一プロジェクトで、1991年の初版から拡張を続け、2025年の Unicode 17.0 では約16万文字(歴史的文字や絵文字を含む)を収録しています。1992年には、その番号を ASCII と両立する形でバイト列にする UTF-8 が考案されました。Webの普及で国境を越えたテキスト交換が爆発的に増えたことが追い風となり、現在ではWebサイトの98%以上が UTF-8 で書かれています。文字コードの歴史は、ローカルな最適化が生んだ分断を、グローバルな標準で統一し直した系譜そのものです。
詳細
最重要の区別 — コードポイントと符号化方式は別物
Unicode を理解する鍵は、「文字に番号を振る」段階と「番号をバイト列にする」段階が分かれていることです。前者がコードポイントで、「あ」には U+3042 という番号が与えられています(U+ に続く16進数が番号)。後者が符号化方式で、同じ番号をバイト列に直す方法として UTF-8・UTF-16・UTF-32 の3種類があります。UTF-32 は全文字を固定4バイトで、UTF-16 は2バイトまたは4バイトで、UTF-8 は1〜4バイトの可変長で表します。「Unicode で保存する」という言い方が曖昧なのはこのためで、実際に保存されるバイト列は符号化方式を決めて初めて定まります。
この変換は、好きな文字で自分の手で確かめられます。文字を入力して、コードポイントとUTF-8バイト列への詰め替えを観察してみてください。
文字を入力するか、カードをクリックしてみてください。各文字がコードポイントを経てUTF-8のバイト列になる様子が見えます。
UTF-8 が勝った理由
3方式のうち事実上の標準となったのは UTF-8 です。決め手は ASCII との完全互換にあります。UTF-8 では ASCII の128文字をそのまま1バイトで表すため、既存の英語のテキスト・設定ファイル・プロトコルは一切変換せずに「正しい UTF-8」として通用します。番号の大きい文字ほどバイト数が増える可変長設計(欧文1バイト、多くの漢字・かな3バイト、絵文字4バイト)で、先頭バイトのパターンを見ればそこが文字の切れ目だと分かる自己同期性も備えています。HTML も JSON も、現代のWeb標準は UTF-8 を前提としています(JSON は仕様で UTF-8 を必須にしています)。なお URL に日本語を含めると %E3%81%82 のような表記になりますが、これは「あ」の UTF-8 バイト列をパーセント記号付き16進数で書き写したもの — 上の図のバイト列そのものです。
文字化けの正体 — 書く規則と読む規則のずれ
文字化けの正体は、データの破損ではなく解釈の食い違いです。バイト列そのものは無事でも、「UTF-8 で書かれたバイト列を Shift_JIS として読む」と、対応表が違うため別の文字列が復元されてしまいます。冒頭の「譁�ュ怜喧縺�」は、「文字化け」を UTF-8 で書いたバイト列 E6 96 87 E5 AD 97 … を、Shift_JIS の規則で2バイトずつ区切って読み直した結果です。Shift_JIS の表にない並びは � (置換文字)になります。
書く人と読む人のダイヤルを別々に回すと、このことがはっきり見えます。真ん中のバイト列は、読む側のダイヤルをいくら回しても1バイトも変わりません。
左は書く人、右は読む人のダイヤルです。まず右のダイヤルを Shift_JIS に回して(項目を押すか、つまみを上下にドラッグ)、同じバイト列を別の規則で読んでみてください。
だからこそ、テキストには「これは何の符号化か」の申告が付きます。HTTP レスポンスの Content-Type ヘッダの charset、HTML の meta charset 宣言、RDBMS の接続・テーブルの文字コード設定がそれです。文字化けの調査とは、この申告と実際のバイト列の食い違いを、データが通る経路(ファイル → アプリ → DB → ブラウザ)のどこで起きたか特定する作業にほかなりません。
ひとつ注意したいのは、化けた文字列をそのまま保存し直すと、本当に壊れることです。読み違えた時点ではバイト列は無事ですが、� に置き換わった文字は元のバイトの情報を持っていないので、その文字列を書き戻すと元には戻せません。文字化けに気づいたら、まず「読み方」を直すのが先です。
BOM と CSV — Excel で開くと化ける理由
「UTF-8 の CSV を Excel で開くと日本語が化ける」は、日本の現場で最もよく出会う文字化けです。CSV というファイル形式には「この中身は何の符号化か」を書く場所がありません。そのため Excel(日本語版の Windows)は、手がかりがなければ昔ながらの Shift_JIS(正確には Windows の CP932)だと推測して開きます。UTF-8 の CSV は、ここで上の部品と同じ「UTF-8 を Shift_JIS として読む」事故になります。
手がかりとして使われるのが BOM(バイト順マーク、Byte Order Mark)です。BOM は U+FEFF という文字をファイルの先頭に置いたもので、UTF-8 なら EF BB BF、UTF-16 のリトルエンディアンなら FF FE というバイト列になります。もともとは UTF-16 で「2バイトをどちらの順に並べたか」(2進数のエンディアン)を申告するための印でしたが、UTF-8 では「この先は UTF-8 です」という署名として使われます。Excel は先頭に EF BB BF を見つけると UTF-8 として開くので、Excel で開いてもらう CSV は「BOM 付き UTF-8」で出力するのが定番の対策です。
ただし BOM は万能ではありません。BOM を知らないプログラムにとっては先頭のゴミで、シェルスクリプトの1行目(#!)が認識されない、CSV の最初の列名に見えない3バイトが付いて列が見つからない、JSON の解析に失敗する、といった別の事故を起こします。Excel のような「推測する読み手」に渡すファイルにだけ付け、プログラム同士でやり取りするファイルには付けない、と使い分けるのが実務的です。
サロゲートペア・絵文字・結合文字 — 「1文字」の落とし穴
Unicode の当初の想定は「65536個(16ビット)あれば全文字が収まる」でしたが、収まりませんでした。UTF-16 はこの想定の上に作られていたため、あふれた文字(多くの絵文字や一部の漢字)は16ビット値2個の組「サロゲートペア」で表す拡張が施されました。JavaScript や Java の文字列がこの UTF-16 ベースであるため、"𩸽".length や "😀".length が 2 を返す、部分文字列の切り出しで文字が真っ二つになる、といった罠が今も現役です。同根の問題として、MySQL の古い utf8(3バイトまでしか格納できない utf8mb3)に絵文字を保存できずエラーになる事故も有名で、RDBMS では utf8mb4 を指定するのが現在の常識です。
さらに、「人間が1文字と感じる単位」はコードポイント1個とも限りません。国旗の絵文字は地域コード2個の組、家族の絵文字は複数の絵文字を接合子でつないだ列、「が」は「か」+濁点の2コードポイントでも表せます。見た目が同じ文字列がバイト列としては別物になり得るため、比較の前に Unicode 正規化(NFC などで表現をそろえる処理)が必要になることがあります。文字数のカウント・切り詰め・検索を素朴に実装すると壊れるのはこのためで、「バイト・コードポイント・見た目の1文字は、それぞれ別の単位である」という理解が、文字コードというテーマの最終的な到達点です。
試問
左の「書く人」のダイヤルでファイルを保存するときの符号化を、右の「読む人」のダイヤルで開くときの符号化を選びます。読む人の「Excel(日本語版)」は、ファイルの中身を見てどの符号化かを推測して開く読み手です。保存する文字列は「文字化け」と「日本語のCSV」から選べ、BOM の欄では、UTF-8 や UTF-16 の先頭に BOM という目印のバイトを付けるかどうかも選べます。
左は書く人、右は読む人のダイヤルです。まず右のダイヤルを Shift_JIS に回して(項目を押すか、つまみを上下にドラッグ)、同じバイト列を別の規則で読んでみてください。
