文字コードとは、UTF-⁠8 と文字化けの仕組み

回 1-2 道 1 数と論理と計算量

文字コードは、文字に番号を振り、その番号をバイトの並びに直す決まりです。Unicode が番号 (U+0041 の形) を決め、UTF-8 がその番号を 1〜4 バイトに直します。書いた決まりと読む決まりが違うと、同じバイト列が別の文字に見えて文字化けします。

目安 50 分1-2 (1-1〜1-8 の中)

読み終えたら文字が UTF-8 で何バイトになるかを決まりから数え、文字化けしたファイルを正しい決まりで読み直せる

この頁の流れ

  1. 1なぜ「あ」は 1 文字なのに 3 バイトなのか
  2. 2文字コードはどう動いているのか
  3. 3動かして確かめる
  4. 4どこで崩れるのか
  5. 5仕事ではどう確かめるのか
  6. 6この回の知識が効いた記録
  7. 7よくある問い
  8. 8確認クイズ
  9. 9説明課題
  10. 10もっと深く
  11. 11一次資料
  12. 12次へ

文字の番号とバイトの並びを切り分け、文字化けしたファイルを正しい決まりで読み直せるようにします。

なぜ「あ」は 1 文字なのに 3 バイトなのか

コンピュータが覚えているのは文字ではなく数で、文字コードはその数と文字を結ぶ決まりです。決まりを知らないと、2 つのよくある失敗を説明できません。

1 つ目は文字数の失敗です。英字だけの試験では通ったのに、日本語の名前を入れたら途中で切れた、という不具合は、欄の上限がバイトで決まっているのに、文字の数とバイトの数を同じ物だと思ったときに起きます。

2 つ目は文字化けです。取引先から届いた CSV (カンマで区切った表のファイル) を開くと、「譌・譛ャ」のような読めない字が並ぶことがあります。ファイルが壊れたのではありません。書いた側と読んだ側で、使った決まりが違うだけです。

文字コードはどう動いているのか

文字コードは「文字に番号を振る段」と「番号をバイトに直す段」の 2 段でできています。番号をバイトに直すことをエンコード (符号化)、バイトから文字に戻すことをデコードと呼びます。

文字に番号を振る Unicode

Unicode (世界中の文字に 1 つずつ番号を振った表) は、文字ごとに符号位置 (コードポイント、文字の番号) を決めています。書き方は U+ の後ろに 16 進数を並べる形で、英字の A は U+0041 です。Unicode の FAQ によると、使える範囲は U+0000 から U+10FFFF までで、Unicode 2.0 (1996年7月) からこの範囲になりました。

Python では、文字から番号を出すのが ord、番号から文字を出すのが chr です。Python の文字列 (str) は、この符号位置の並びです。

英数字の土台の ASCII と制御文字

ASCII (英数字と記号の古い表) は 0〜127 の番号で、Unicode の U+0000〜U+007F と同じ並びです。そのうち 0〜31 番の 32 字は、画面に字を出さずに改行やタブのような動きを指す制御文字で、RFC 20 (ASCII を定めた IETF の文書) の表の 0 列と 1 列に並んでいます。

動かす 制御文字の番号

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

'A' 65
' ' 32
'\t' 9
'\r' 13
'\n' 10
b'1,2\r\n'

タブは 9 (HT)、CR (行の頭に戻す) は 13、LF (次の行へ送る) は 10 です。行の終わりが CR と LF の 2 字か LF の 1 字かの違いも、画面では見えない番号の違いです。

番号をバイトに直す UTF-8

UTF-8 は、符号位置をバイト (8 ビットのまとまり) の並びに直す決まりの 1 つで、番号が大きいほどバイトが増えます。何バイトになるかは、RFC 3629 (UTF-8 を定めた IETF の文書) の表で決まっています。

符号位置の範囲バイト数先頭のバイトの形
U+0000〜U+007F10xxxxxxx
U+0080〜U+07FF2110xxxxx
U+0800〜U+FFFF31110xxxx
U+10000〜U+10FFFF411110xxx

ただし 3 バイトの範囲のうち U+D800〜U+DFFF は、UTF-16 (別の符号化方式) が 2 つ組で使うために取ってある番号 (サロゲート) で、文字そのものは表しません。RFC 3629 は、この範囲を UTF-8 で書くことを禁じています。

2 バイト目から後ろはどれも 10xxxxxx の形で、先頭のバイトの頭の 1 の数がバイト数を表します。だから読む側は、先頭の 1 バイトでどこまでが 1 文字かが分かります。

文字を表で番号にして保存し、読むときに同じ表で戻す流れ。同じバイト列を別の表で読むと化ける

日本語で使う文字コードの種類

日本語の文字コードは今は UTF-8 が中心で、古いファイルには Shift_JIS、EUC-JP、ISO-2022-JP が残っています。ブラウザの文字コードを定める WHATWG の Encoding Standard は、この 3 つを古い日本語の文字コードとしてまとめ、新しい形式には UTF-8 を求めています。同じ「あ」でも、決まりごとにバイトが違います。

動かす 同じ「あ」を 5 つの決まりで

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

shift_jis 2
  82 a0
euc_jp 2
  a4 a2
iso2022_jp 8
  1b 24 42 24 22 1b 28 42
utf-8 3
  e3 81 82
utf-16-le 2
  42 30
文字コード「あ」WHATWG の扱い
Shift_JIS2 バイト古い日本語の文字コード
EUC-JP2 バイト古い日本語の文字コード
ISO-2022-JP8 バイト古い日本語の文字コード
UTF-83 バイト新しい形式に求める
UTF-162 バイト読めるが書き手は無い

ISO-2022-JP の 8 バイトのうち、頭の 1b 24 42 は日本語の表に切り替える印、終わりの 1b 28 42 は ASCII に戻す印で、「あ」そのものは真ん中の 24 22 です。この 2 つの印は WHATWG の書き手の手順にも書いてあります。UTF-16 の 42 30 は、U+3042 を小さい桁から並べた物です。

動かして確かめる

文字のバイト数は、全部の文字をコードで出せば、覚えなくても確かめられます。

文字ごとの符号位置とバイト数

英字、アクセント付きの英字、ひらがな、漢字、絵文字を UTF-8 に直します。絵文字は chr(0x1F600) で作り、表示の列には「顔」と書きます。

動かす 符号位置と UTF-8 のバイト

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

A 65 U+0041 1 41
é 233 U+00E9 2 c3 a9
あ 12354 U+3042 3 e3 81 82
漢 28450 U+6F22 3 e6 bc a2
顔 128512 U+1F600 4 f0 9f 98 80

左から、文字、10 進の番号、符号位置、バイト数、バイトの中身です。RFC 3629 の 4 つの範囲に 1 つずつ入り、先頭のバイトも 41 は頭が 0、c3 は 110、e3 は 1110、f0 は 11110 と、表の形どおりです。

文字数とバイト数は別の数

len は文字列なら符号位置の数を、バイト列ならバイトの数を返します。同じ中身でも、どちらを数えるかで答えが変わります。

動かす len とバイト数

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

4
11
2

4 文字の文字列が、UTF-8 では 1 + 3 + 3 + 4 で 11 バイトです。最後の "e" + chr(0x301) は e の後ろにアクセントの記号 (U+0301) をつないだ物で、画面では é と同じ 1 文字に見えても len は 2 です。RFC 3629 も、é は U+00E9 の 1 つでも、U+0065 と U+0301 の 2 つでも表せると書いています。

どこで崩れるのか

文字コードは「書いた決まりと読む決まりが同じ」という前提で成り立っています。この前提を外すと、エラーも出ずに字が変わります。

別の決まりで読むと文字化けする

UTF-8 で書いた「日本」のバイト列を、Shift_JIS として読みます。逆に、Shift_JIS で書いたバイト列を UTF-8 として読みます。errors="replace" は、読めないバイトを U+FFFD (置き換えの印の文字) にして先へ進む指定です。

動かす 文字化け

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

e6 97 a5 e6 9c ac
譌・譛ャ
93 fa 96 7b
���{
82 a0

UTF-8 の 6 バイトを Shift_JIS で読むと、2 バイトと 1 バイトの区切りがずれて「譌・譛ャ」の 4 文字になります。逆向きでは、読めないバイトが置き換えの印になり、最後の 7b だけが ASCII の { として読めます。最後の行は、「あ」の Shift_JIS が 82 a0 で、UTF-8 の e3 81 82 とは別物だという確かめです。

文字コードと文字化け

Python の decode は、errors を書かなければ 'strict' で、読めないバイトに当たると例外を出して止まります。replace は先へ進む代わりに、元のバイトの情報を捨てます。

その決まりに無い文字は書けない

Shift_JIS の表には、さきほどの絵文字がありません。書こうとすると止まります。

動かす 表に無い文字

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

illegal multibyte sequence
4

UTF-8 は、サロゲートを除く U+10FFFF までの全部の符号位置を書けます。Shift_JIS には書けない字があり、古い仕組みに渡すときにここで落ちます。

Excel で開くと化ける CSV と BOM

UTF-8 で書いた CSV を Excel で開くと化けるのは、ファイルの頭に BOM が無いからです。BOM (バイト順マーク) は U+FEFF という見えない字で、UTF-8 では EF BB BF の 3 バイトです。Unicode の FAQ は、UTF-8 の BOM は「UTF-8 だ」という目印 (署名) にすぎないと書いています。Microsoft の手引きは、BOM を付けて保存した UTF-8 の CSV なら Excel で普通に開け、無ければ「データの取得」から開くよう案内しています。

Python では、utf-8-sig で書くと頭に BOM が付きます。

動かす BOM を付けて書く

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

e5 90 8d e5 89 8d
ef bb bf e5 90 8d
3
'\ufeff名前'
True

BOM を付けると 3 バイト増えます。逆に、BOM 付きのファイルを utf-8 で読むと頭に \ufeff が残り、1 列目の見出しが「名前」と一致しなくなります。見出しで列を探すコードは、ここで落ちます。utf-8-sig で読めば、BOM があれば外し、無ければそのまま読みます。Unicode の FAQ は BOM を受け取ると思っていない読み手もいると書いているので、Excel 向け以外のファイルには付けないのが無難です。

仕事ではどう確かめるのか

文字コードは「何の決まりで読み書きしているか」を毎回書いて確かめます。見る所は次の通りです。

  • ファイルを開くときに決まりを書く。Python なら open(path, encoding="utf-8") のように、既定に任せない
  • 欄の上限が「文字」か「バイト」かを仕様で確かめ、日本語と絵文字を入れた試験を足す
  • 化けた字を見たら、まずバイトを 16 進で出す。b.hex(" ") の先頭が e3 や e6 なら UTF-8 の 3 バイトの字、82 や 93 なら Shift_JIS の可能性を疑う

文字コードを見分ける

バイト列だけから文字コードを確実に当てる方法は無いと、Python の公式文書も書いています。ただし UTF-8 には決まった形があり、でたらめなバイト列は UTF-8 として読めません。そこで、BOM を見て、UTF-8 で読めるかを試し、だめなら cp932 (Windows の Shift_JIS) で読む、の順に当てます。

動かす 決まりを当てる

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

utf-8 utf-8
cp932 cp932
utf-8-sig utf-8-sig

3 つとも当たりました。ただし、短いバイト列や英数字だけのデータは、どの決まりでも読めてしまうことがあります。当てた結果は、中身を目で見て確かめてから使います。

UTF-8 に直す

UTF-8 に直す手順は、正しい決まりで読み、UTF-8 で書き直す、の 2 歩です。Windows で作った Shift_JIS のファイルには NEC や IBM が足した字 (① など) が入ることがあり、Python の shift_jis では読めません。WHATWG もブラウザの Shift_JIS の表にこの字を含めています。Python では cp932 で読みます。

動かす cp932 から UTF-8 へ

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

93 fa 96 7b 2c 87 40
e6 97 a5 e6 9c ac 2c e2 91 a0
illegal multibyte sequence

① は cp932 では 87 40 の 2 バイトで、UTF-8 では e2 91 a0 の 3 バイトです。同じバイト列を shift_jis で読むと、① の所で止まります。手元のファイルなら、次の形で直します。

手元のファイルを UTF-8 に直す

src = "in.csv"
dst = "out.csv"
with open(src, "r",
        encoding="cp932") as f:
    text = f.read()
with open(dst, "w",
        encoding="utf-8") as f:
    f.write(text)

符号位置の名前を引く

見た目が同じ字でも、番号が違えば別の字です。マイナスに見える字を 4 つ並べ、番号と名前を引きます。

動かす 符号位置の名前を引く

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

U+002D HYPHEN-MINUS
U+2212 MINUS SIGN
U+FF0D FULLWIDTH HYPHEN-MINUS
U+2010 HYPHEN

キーボードで打つマイナスは U+002D で、数式のマイナスは U+2212 です。検索で引っかからない、数に直せない、というときは、この 1 行で番号を確かめます。

この回の知識が効いた記録

UTF-8 の決まりに反するバイト列で検査をすり抜ける攻撃は、仕様そのものに「実際に使われた」と記録されています。

RFC 3629 (2003年11月) の安全の節は、2F 2E 2E 2F (/../、上の階層へ戻る道) を止める検査が、決まりに反する 2F C0 AE 2E 2F を通してしまう例を挙げ、この手口が 2001年に Web サーバーを襲ったウイルスで使われたと書いています。C0 AE は「.」を無理に 2 バイトで書いた形です。崩れた前提は「バイトの段で検査した物と、文字に戻した後の物が同じ」です。

動かす 決まりに反する並び

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

/��./
invalid start byte

RFC は、読み手はこの並びを読まないよう守らなければならない (MUST) と定めていて、Python は C0 を先頭のバイトと認めず「.」に戻しません。出典は RFC 3629 (IETF、英語) の 3 節と 10 節です。

よくある問い

UTF-8 とは何ですか

Unicode の符号位置を 1〜4 バイトの並びに直す決まりで、RFC 3629 が定めています。U+007F までは ASCII と同じ 1 バイトです。Unicode の FAQ は、Web で最もよく使われる形だと書いています。

文字コードとエンコードの違いは何ですか

文字コードは文字とバイトを結ぶ決まりそのもので、エンコードはその決まりで文字をバイトに直す作業です。Unicode は番号を振る表、UTF-8 はその番号をバイトに直す方式で、Shift_JIS は番号の振り方から別の文字コードです。

文字化けしたデータは元に戻せますか

バイト列が残っていれば、正しい決まりで読み直すと戻ります。errors="replace" で置き換えた後の文字列からは戻せません。置き換えは U+FFFD という 1 つの印にまとめるので、元のバイトの情報が消えるからです。

ファイルを UTF-8 に変換するにはどうしますか

元の文字コードで読み、UTF-8 で書き直します。Windows で作った Shift_JIS のファイルは、Python なら encoding="cp932" で読み、encoding="utf-8" で書きます。Excel で開く CSV なら、utf-8-sig で書いて BOM を付けます。

ファイルの文字コードはどうやって見分けますか

バイト列だけから確実に当てる方法は無いので、頭の BOM、UTF-8 として読めるか、cp932 として読めるか、の順に試します。最後に、読めた中身が日本語として意味が通るかを目で確かめます。

確認クイズ

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。4 問すべてに正しく答えると、この回は修了で、石が緑に変わります (この端末のブラウザに残ります)。間違えた問いは、あとから解き直せます。

1 問目

ひらがなの「あ」(U+3042) は、UTF-8 で何バイトになりますか。

2 問目

UTF-8 で書いた CSV を開いたら「譌・譛ャ」のような字が並びました。最初に疑うことはどれですか。

3 問目

次のコードを動かすと、何が出ますか。

BOM を付けたバイト数

b = "日本".encode("utf-8-sig")
print(len(b))

4 問目

Python で CSV を書き、日本の Excel で開いても化けないようにしたいとき、open に渡す指定はどれですか。

説明課題

次のことを、自分の言葉で 3〜4 行にまとめてください。書いたら、下の模範解答を開いて比べてください。

  • 「漢」が UTF-8 で何バイトになるかを、決まりから数える手順
  • Windows で作った Shift_JIS の CSV を、UTF-8 に直す手順と、そのときに気をつけること

模範解答

自分の説明を書いてから開いて、比べてください。

この回の先の一歩

文字化けの切り分けは、データを受け渡す仕事なら毎週のように出会います。いまの自分が転職までの道のどの段にいるかは、転職診断で確かめられます。

もっと深く

この回の続きを、ゆめさくのコースの章や道具で深められます。頁の中の確認クイズに答えたあと、手を動かす量を増やしたいときに使ってください。

一次資料

どれも 2026年10月26日に開いて、題と中身を確かめました。

次へ

道 1 数と論理と計算量の回

  1. 1-12 進数と 16 進数桁の重み、変換、表せる範囲頁で読む
  2. 1-2文字コードと文字化けUnicode、UTF-8、Shift_JIS、BOMこの回
  3. 1-3計算量を数えるBig-O、ループの回数頁で読む
  4. 1-4論理と真理値表AND、OR、NOT、ド・モルガン準備中
  5. 1-5リトルエンディアンとビッグエンディアンバイトの並び順、ネットワーク、ファイル頁で読む
  6. 1-6Base64 の仕組み64 字の表、= の詰め物、長さ 4/3頁で読む
  7. 1-7情報量とハフマン符号化ビット、確からしさ、圧縮頁で読む
  8. 1-8チューリングマシンと停止問題テープ、状態、計算できない問題頁で読む

道 1 の頁へ戻るほかの道を見る