文字化けと文字コードを理解する
文字化けに遭遇したので原因を調べてみました。
日本語の部分のみ文字化けしており、英字はそのまま出力される形の文字化けです。
こんな感じのよく見るやつ
繧オ繝ウ繝励Ν譁�蟄怜喧縺代��English is not garbled 1
Shift-JISで誤ってUTF-8を解釈した場合によく見られる文字化けの例です。
文字化けの原因
文字化けの根本的な原因は、送り手と受け手の文字コードが一致していないからです。
データベース、またはファイル⇒プログラム⇒ウェブサーバー⇒ブラウザ
このような構成だとして、この流れのどこかで文字コードが不一致だということです。
今回は受け手側であるHTMLでShift-JISを指定しているにもかかわらず、送り手側のプログラムがUTF-8でファイルを読み込んだことが直接的な原因でした。
英数字は文字化けしにくい
上の例でも分かるように半角英数字は、ほとんどの場合文字化けしません。これはUTF-8でもShift-JISでも半角英数字のデータはほぼ同じだからです。
一方で、日本語のようなマルチバイト文字は、文字コードによってデータの形式が大きく変わるため不一致が起きると文字化けしてしまいます。
public static void main(String[] args) {
// 元の日本語文字列
String original = "サンプル文字化け。English is not garbled 1";
// UTF-8でバイト列に変換
byte[] utf8Bytes = original.getBytes(StandardCharsets.UTF_8);
// Shift_JISとして誤ってデコード(文字化けを再現)
String mojibake = new String(utf8Bytes, Charset.forName("Shift_JIS"));
// 結果表示
System.out.println("元の文字列: " + original);
System.out.println("UTF-8バイト列: " + bytesToHex(utf8Bytes));
System.out.println("Shift_JISで誤ってデコードした結果(文字化け): " + mojibake);
}
// バイト列を16進数文字列に変換するユーティリティ
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString().trim();
}
これを実行すると
元の文字列: サンプル文字化け。English is not garbled 1
UTF-8バイト列: E3 82 B5 E3 83 B3 E3 83 97 E3 83 AB E6 96 87 E5 AD 97 E5 8C 96 E3 81 91 E3 80 82 45 6E 67 6C 69 73 68 20 69 73 20 6E 6F 74 20 67 61 72 62 6C 65 64 20 31
Shift_JISで誤ってデコードした結果(文字化け): 繧オ繝ウ繝励Ν譁�蟄怜喧縺代��English is not garbled 1
変換エラーは出ずに誤った解釈のまま出力されます。
上記はjavaの結果ですが、Pythonで同じように書いてみると挙動が違います。
# UTF-8でエンコードされた日本語文字列
original_text = "サンプル文字化け。English is not garbled 1"
# UTF-8でバイト列に変換
utf8_bytes = original_text.encode('utf-8')
# Shift_JISとして誤ってデコード(文字化けを再現)
try:
mojibake_text = utf8_bytes.decode('shift_jis')
except UnicodeDecodeError as e:
mojibake_text = f"デコードエラー: {e}"
# 結果表示
print("元の文字列:", original_text)
print("UTF-8バイト列:", utf8_bytes)
print("Shift_JISで誤ってデコードした結果(文字化け):", mojibake_text)
元の文字列: サンプル文字化け。English is not garbled 1
UTF-8バイト列: b'\xe3\x82\xb5\xe3\x83\xb3\xe3\x83\x97\xe3\x83\xab\xe6\x96\x87\xe5\xad\x97\xe5\x8c\x96\xe3\x81\x91\xe3\x80\x82English is not garbled 1'
Shift_JISで誤ってデコードした結果(文字化け): デコードエラー: 'shift_jis' codec can't decode byte 0x87 in position 14: illegal multibyte sequence
UnicodeDecodeErrorによって変換が中断されています。
これは中途半端なデータを生成しないというPythonの特性です。
Shift-JISルールではこのバイト列を解釈できないと判断したという賢さでもありますね。
そもそもマルチバイト文字って?
マルチバイト文字は、1文字を2バイト以上で表現する文字のことです。
| 種類 | バイト数 | 例 | 備考 |
|---|---|---|---|
| シングルバイト文字 | 1バイト | A, B, 1, ! | ASCII文字など |
| マルチバイト文字 | 2バイト以上 | あ, 漢, 語, 😀 | 日本語、中国語、絵文字など |
文字コードの歴史
せっかくなので文字コードの歴史もひも解いてみます。
シングルバイト文字は多くの文字コードの「もと」になっています。そして、そこからUTF-8に至るまでには、コンピュータが多言語を扱う上で直面した課題と、それを克服するための文字コードの進化の歴史があります。
ASCIIは英語圏の文字を扱うには十分でしたが、世界には多様な言語が存在します。アクセント記号付きのアルファベット(フランス語やドイツ語)、キリル文字(ロシア語)、ギリシャ文字、そして日本語のような漢字を含む膨大な文字を持つ言語など、ASCIIの128文字では全く足りません。そこで、各地域や国で、ASCIIを拡張する形で独自の文字コードが作られていきました。
日本語の文字コード
- Shift_JIS (シフトJIS): Microsoftが開発し、Windowsで広く使われた文字コードです。ASCII互換性があり、半角カナや全角文字(漢字、ひらがな、カタカナ)を扱えます。
- EUC-JP (日本語EUC): UNIX系システムで主に使われた文字コードです。これもASCII互換性があります。
- JISコード (ISO-2022-JP): 主に電子メールなどで使われた文字コードです。エスケープシーケンスを使って文字セットを切り替える特徴があります。
Unicode&UTF-8の登場
文字コードが乱立するなか、文字化けの問題が深刻化し、Unicodeが開発されました。
Unicodeの基本的な思想は、世界中のあらゆる文字を、たった一つの文字集合に含め、それぞれに固有の番号(コードポイント)を割り当てるというものです。これにより、どんな言語の文字であっても、一つの統一されたルールで識別できるようになりました。
Unicodeはどの文字が、どの番号(コードポイント)に対応するかを定義しますが、それを実際にコンピュータ上でどのようにバイト列として表現するかは、また別の問題です。この表現方法を「文字符号化方式」と呼びます。Unicodeには複数の文字符号化方式が存在し、その中で現在最も普及しているのがUTF-8です。
Shift-JISからUTF-8への変換はうまくいくことが多い
UTF-8はあらゆる言語の文字を扱える文字コードなので、ほとんどの場合は問題なく変換されますが、一部機種依存文字や特定の記号は変換できないことがあるということです。
String original = "文字化けテスト";
// SJISバイト列
byte[] sjisBytes = original.getBytes(Charset.forName("Shift_JIS"));
// SJISとしてデコード
String decoded = new String(sjisBytes, Charset.forName("Shift_JIS"));
byte[] utf8Bytes = decoded.getBytes(StandardCharsets.UTF_8);
String utf8String = new String(utf8Bytes, StandardCharsets.UTF_8);
System.out.println(decoded);
文字化けテスト
このように、正しい順序で変換すれば文字化けは防げます。
まとめ
最近のFWや開発環境はUTF-8がデフォルトであることが多いです。
そんなに意識しなくとも賢く扱ってくれる場合が多いのですが、中にはShift-JISを使用するシステムもあるのでしっかり勉強したい部分ですね。
Discussion