1. UTF-8 — 現代のエンコーディングの王者
プログラマの仕事では、ファイル、ネットワークプロトコル、データベース、Web サービスなど、さまざまなエンコーディングに出会います。もし UTF-8 と ISO-8859-1 の違いを知らないと、「自分のマシンでは動く」のに、フランスの同僚の環境や既定設定が異なるクラウドでは壊れる、といった落とし穴にはまりがちです。
本講義では、現場の Java 開発で出会う代表的な 3 つのエンコーディングを取り上げます:
- UTF-8 — 現代のユニバーサル標準。
- UTF-16 — Java の文字列の内部形式で、Windows でもよく使われる。
- ISO-8859-1 (Latin-1) — 西ヨーロッパ言語向けの古いが今も見かける 1 バイトエンコーディング。
さっそく見ていきましょう。
概要
UTF-8(Unicode Transformation Format, 8-bit)は、巨大な Unicode テーブルのどの文字でも表現できるエンコーディングです(キリル文字、中国語の漢字、絵文字、さらには希少な古代文字まで)。しかも賢くできていて、最も一般的な文字(英字・数字・句読点)は 1 バイト、その他は 2、3、あるいは 4 バイトで表現します。
事実: 現代のほぼすべてのサイト、ほとんどの JSON/XML ファイル、そして Java のソースコードも既定で UTF-8 を採用しています。
1 文字に必要なバイト数は?
- ASCII の文字(英字・数字・句読点) — 1 バイト。
- キリル文字、欧州系のアクセント付き文字 — 2 バイト。
- 中国語・日本語・韓国語の表意文字 — 3 バイト。
- 非常に稀な文字、絵文字 — 4 バイト。
例:
| 文字 | Unicode コード | UTF-8(バイト列) |
|---|---|---|
| A | |
|
| Ya | |
|
| € | |
|
| 😀 | |
|
なぜ UTF-8 が優れているのか
- ASCII と後方互換: ファイルが英字だけなら、ASCII と完全に同じ見た目になります。
- コンパクト: 英語中心の短いテキストでは UTF-8 が最小サイズになります。
- 国際対応: 絵文字を含むあらゆる言語をサポート。
- Web の標準: HTML、CSS、JSON、XML、JavaScript は既定で UTF-8。
Java での使用例
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class Utf8Demo {
public static void main(String[] args) throws Exception {
// UTF-8 で文字列をファイルに書き込む
String text = "こんにちは、世界! 😀";
Files.write(Paths.get("utf8.txt"), text.getBytes(StandardCharsets.UTF_8));
// UTF-8 でファイルを読み込む
String read = Files.readString(Paths.get("utf8.txt"), StandardCharsets.UTF_8);
System.out.println(read); // 出力: こんにちは、世界! 😀
}
}
ここでは、書き込み前に文字列を明示的に UTF-8 にエンコードし、読み込み時にも同じエンコーディングを明示しています。これにより、絵文字や多言語の文字が含まれていても、文字化けせずに保存・復元できます。
いつ UTF-8 を使うべきか?
基本的に常に。よほど強い理由がない限り、現代のアプリケーションではこれが最適な選択です。
2. UTF-16 — Java の内部形式
UTF-16 は Unicode ファミリーの別のエンコーディングです。UTF-8 と違い、多くの場合 2 バイトで 1 文字を表します。Java は内部で文字列(String)や文字(char)をこの形式で保持します。ただし注意点として、一部の文字(希少な表意文字や絵文字など)は 4 バイト(2 つのサロゲートペア)を必要とします。
UTF-16 はどこで使われるか
- Java の内部: すべての文字列と文字は UTF-16。
- Windows: 多くのシステムファイルが UTF-16 を使用(例: Notepad は既定で UTF-16 LE で保存)。
- 一部のプロトコルやフォーマット: 例えば XML は UTF-16 の場合があります。
例: UTF-16 の見え方
| 文字 | Unicode コード | UTF-16(バイト列) | 備考 |
|---|---|---|---|
| A | |
|
2 バイト |
| Ya | |
|
2 バイト |
| € | |
|
2 バイト |
| 😀 | |
|
4 バイト(サロゲートペア) |
Java での使用例
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class Utf16Demo {
public static void main(String[] args) throws Exception {
String text = "こんにちは、世界! 😀";
// エンコーディング UTF-16 を使ってファイルに書き込む
Files.write(Paths.get("utf16.txt"), text.getBytes(StandardCharsets.UTF_16));
// エンコーディング UTF-16 を指定して読み込む
String read = Files.readString(Paths.get("utf16.txt"), StandardCharsets.UTF_16);
System.out.println(read); // 元の文字列を正しく出力
}
}
重要なポイント: 書き込みと読み込みで同じエンコーディングを指定すること。UTF-16 で保存しておきながら、既定(例: UTF-8)で読み込むと、文字が「ぐちゃぐちゃ」になります。
UTF-16 の特徴
- 多くの文字が固定長: 2 バイト。
- サロゲートペア: 一部の文字(絵文字など)は 4 バイト。
- Byte Order Mark (BOM): 先頭にバイト順序を示すヘッダ(LE — little endian、BE — big endian)が付くことが多い。Java は通常 BOM を解釈します。
- ASCII と互換ではない: ASCII や UTF-8 を期待するエディタで UTF-16 ファイルを開くと、ゼロバイトや謎の記号が多く見えるでしょう。
いつ UTF-16 を使うべきか?
- UTF-16 を要求する特殊なシステムやファイル(例: Windows 系プログラムとの連携)に対応する場合。
- 通常のアプリやファイルには UTF-8 を使うのが無難。
4. ISO-8859-1 (Latin-1) — 昔からの名残
ISO-8859-1、通称 Latin-1 は、1980 年代に設計された 1 バイトのエンコーディングです。256 文字をサポートし、英字、西欧のアクセント付き文字(é、ü、ç など)、句読点、各種記号が含まれます。
事実: このエンコーディングにはキリル文字、ギリシャ文字、アラビア文字、中国語などの非ラテン系文字は含まれていません。
ISO-8859-1 はどこで見かけるか
- 古いファイルやプログラム(特にヨーロッパ)。
- 一部のデータベースやプロトコルの「既定」。
- HTTP ヘッダ(標準ではエンコーディング未指定のテキストは ISO-8859-1 とみなされますが、実運用では稀)。
例: ISO-8859-1 の見え方
| 文字 | Unicode コード | ISO-8859-1(バイト) | 備考 |
|---|---|---|---|
| A | |
|
ASCII と同じ |
| é | |
|
フランス語 |
| ü | |
|
ドイツ語 |
| Ya | |
— | その文字は存在しない! |
Java での使用例
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.charset.Charset;
public class Latin1Demo {
public static void main(String[] args) throws Exception {
String text = "Bonjour, ça va? Café!"; // フランス語のテキスト
Files.write(Paths.get("latin1.txt"), text.getBytes(StandardCharsets.ISO_8859_1));
// ISO-8859-1 で読み込む
String read = Files.readString(Paths.get("latin1.txt"), StandardCharsets.ISO_8859_1);
System.out.println(read); // 正しく読み取れる!
// キリル文字を書き込んでみる
String cyrillic = "こんにちは、世界!";
try {
Files.write(Paths.get("badlatin1.txt"), cyrillic.getBytes(StandardCharsets.ISO_8859_1));
} catch (Exception e) {
System.out.println("エラー: ISO-8859-1 にはキリル文字を保存できません!");
}
}
}
ISO-8859-1(Latin-1)は西ヨーロッパの文字だけをサポートします。たとえばフランス語・ドイツ語・スペイン語のアクセント付き文字は問題なく読み書きできます。
しかしキリル文字はこのエンコーディングには存在せず、保存しようとするとエラーや文字化けが発生します。多言語のテキストには、より広範囲をカバーする UTF-8 や UTF-16 を使うべきです。
ISO-8859-1 の特徴
- 対応言語が限定的: 西ヨーロッパ言語のみ。
- 1 バイト/文字: コンパクトだが非常に制約が大きい。
- 未対応文字は破損: キリル文字や表意文字を保存すると、疑問符やゴミに化ける。
いつ ISO-8859-1 を使うべきか?
- このエンコーディングを必須とする非常に古いシステムやデータベースと連携する場合のみ。
- 現代的な用途には推奨されません。
5. 役立つポイント
エンコーディングの比較表
| エンコーディング | 1 文字あたりのバイト | 対応言語 | ASCII 互換 | 主な用途 |
|---|---|---|---|---|
|
1-4 | すべて | 完全 | Web, Java, JSON, XML, Linux |
|
2 または 4 | すべて | なし | Java の内部, Windows, XML |
|
1 | 西ヨーロッパ | 完全 | 古いプログラム・DB・HTTP |
エンコーディングの選び方
- UTF-8 — 用途の 99% はこれ。ASCII と互換、全言語対応、英語テキストは最小サイズ、Web と Java の標準。
- UTF-16 — 仕様で要求される場合や Windows 系プログラムとの統合が必要な場合のみ。
- ISO-8859-1 — 古いシステムとの後方互換のためだけに使用。キリル文字、ギリシャ語、アラビア語などの保存には使わないこと。
ファイルのエンコーディングを知るには?
- エンコーディングを表示できるエディタ(例: Notepad++、VS Code)で開く。
- 「文字化け」が見えたら、別のエンコーディングで開いてみる。
- Linux のターミナルでは file ファイル名 — コマンドで推測できる場合があります。
Java でエンコーディングを指定するには?
java.nio.charset パッケージのクラスを使います:
- StandardCharsets.UTF_8
- StandardCharsets.UTF_16
- StandardCharsets.ISO_8859_1
またはエンコーディング名を指定する:
Charset windows1251 = Charset.forName("Windows-1251");
Charset utf8 = Charset.forName("UTF-8");
6. 実践: エンコーディングを取り違えるとどうなる?
UTF-8 で文字列を書き込み、その後 ISO-8859-1 として読み込んでみましょう:
import java.nio.file.*;
import java.nio.charset.*;
public class EncodingMismatchDemo {
public static void main(String[] args) throws Exception {
String text = "こんにちは、世界!"; // 日本語
// UTF-8 で書き込む
Files.write(Paths.get("utf8demo.txt"), text.getBytes(StandardCharsets.UTF_8));
// ISO-8859-1 として読む(間違い!)
String wrong = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.ISO_8859_1);
System.out.println("誤った読み込み: " + wrong);
// 正しく読み込む
String correct = Files.readString(Paths.get("utf8demo.txt"), StandardCharsets.UTF_8);
System.out.println("正しい読み込み: " + correct);
}
}
結果:
- 最初のケースでは「文字化け」— 意味不明な文字列になります。
- 2 番目のケースでは — 正しい日本語のテキストが表示されます。
7. エンコーディングでよくあるミス
エラー №1: ファイルを誤ったエンコーディングで読む。 ファイルが UTF-8 で保存されているのに、ISO-8859-1 や Windows-1251 として読み込むと、非 ASCII 文字がすべてゴミになります。典型的な「自分の PC では動くが、同僚の環境では動かない」問題です。
エラー №2: 未対応の文字を 1 バイトエンコーディングに保存しようとする。 ISO-8859-1 にキリル文字や表意文字を保存しようとすると、Java は疑問符に置き換えたりエラーを投げたりします。データは不可逆に失われます。
エラー №3: システムの既定エンコーディングを使う。 一部の Java メソッド(例: new FileReader("file.txt"))ではエンコーディングを明示できず、システム既定が使われます。マシン A では UTF-8、B では Windows-1251、C ではさらに別物、ということも。必ずエンコーディングを明示できるメソッドを使いましょう。
エラー №4: UTF-16 のファイルを UTF-8 として開く。 UTF-16 で保存されたファイルを UTF-8 として開くと、ゼロバイトや奇妙な記号だらけになります。バイト列の解釈が誤っているためです。
GO TO FULL VERSION