CodeGym /コース /JAVA 25 SELF /基本の文字エンコーディング: UTF-8, UTF-16, ISO-8859-1

基本の文字エンコーディング: UTF-8, UTF-16, ISO-8859-1

JAVA 25 SELF
レベル 37, レッスン 1
使用可能

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
U+0041
41
Ya
U+042F
D0 AF
€
U+20AC
E2 82 AC
😀
U+1F600
F0 9F 98 80

なぜ 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
U+0041
00 41
2 バイト
Ya
U+042F
04 2F
2 バイト
€
U+20AC
20 AC
2 バイト
😀
U+1F600
D8 3D DE 00
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
U+0041
41
ASCII と同じ
é
U+00E9
E9
フランス語
ü
U+00FC
FC
ドイツ語
Ya
U+042F
— その文字は存在しない!

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 互換 主な用途
UTF-8
1-4 すべて 完全 Web, Java, JSON, XML, Linux
UTF-16
2 または 4 すべて なし Java の内部, Windows, XML
ISO-8859-1
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 として開くと、ゼロバイトや奇妙な記号だらけになります。バイト列の解釈が誤っているためです。

1
タスク
JAVA 25 SELF, レベル 37, レッスン 1
ロック未解除
旧式の通信システムを通じてキリル文字を送信しようとする試み 📟
旧式の通信システムを通じてキリル文字を送信しようとする試み 📟
1
タスク
JAVA 25 SELF, レベル 37, レッスン 1
ロック未解除
言語学的実験:異なるエンコーディングにおける単語の重さ ⚖️
言語学的実験:異なるエンコーディングにおける単語の重さ ⚖️
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION