🕌

デフォルトエンコーディングに依存って何?[主にJava]

に公開

はじめに

こんにちは。
プログラミング初心者wakinozaと申します。
Java勉強中に調べたことを記事にまとめています。

十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。

記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。

この記事のテーマ

ファイル操作などを記述していた際、SpotBugs先生に「デフォルトエンコーディングに依存してるよ」と怒られてしまいました。
とはいえ、そもそもデフォルトエンコーディングが何なのかやや曖昧だったので、この機に調べてみました。

この記事では、「デフォルトエンコーディングに依存とはなんぞや?」というテーマで初心者が調べた内容をまとめています。

対象読者

  • Java勉強中の方
  • Javaにおけるデフォルトエンコーディングについて知りたい方

目次

1. Java開発環境での文字情報
2. 文字コードとは
3. 文字化けが起こる理由
4. 文字コードの指定方法
5. 文字コードの指定例

本文

1. Java開発環境での文字情報

Javaの内部環境では、文字情報はUTF-16という文字コードで扱われています。

UTF-16は、世界中の文字を統一的に扱うUnicodeという文字集合を、16ビット単位で表現する文字コードです。
Java言語で文字情報を扱うプリミティブ型のchar型が16ビットであるのは、内部で16ビットのUTF-16を扱っているためです。

Javaでは、JVM(Javaの仮想マシン)が扱う文字情報はUTF-16と定められています。
このルールによって、どのOSや環境で実行しても、Javaの内部環境であれば世界中の文字を統一的に扱うことができるのです。

2. 文字コードとは

しかし、UTF-16で表される文字情報はそのままでは、Javaの外部環境で取り扱いできません。

外部環境では、データはバイト列(8ビット単位)で取り扱われます。そのため、16ビットが最小単位であるUTF-16の文字情報を、そのまま扱うことができないのです。

外部環境が扱えるようにするには、文字情報をバイト列に変換する必要があります。
文字情報をバイト列に変換することを「エンコーディング(符号化)」、バイト列を文字情報に変換することを「デコーディング(復号化)」と言います。

この変換の際に、どのルールで変換するかを定めたものが「文字コード」です。

3. 文字化けが起こる理由

文字コードは、UTF-16以外にも数多く存在します。
この文字コードの多様性が、「文字化け」の原因となるのです。

ある文字列を文字コードAでエンコーディングしたとします。
そのまま、同じ文字コードAでデコーディングされれば、文字情報は正しく復元されます。

しかし、もしエンコーディングと別の文字コードBでデコーディングされると、バイト情報は全く別の符号パターンで変換され、意味不明な文字となってしまいます。
これが「文字化け」です。

実は、デフォルトとして設定されている文字コード「デフォルトエンコーディング」は、OSなどの環境によって異なります。

OS デフォルトエンコーディング
Windows(日本語版) MS932(Shift_JIS互換)
macOS / Linux UTF-8

そのため、Windows環境でエンコーディングしたバイト情報を、Linux環境でデコーディングすると、デフォルトエンコーディングの違いによって文字化けする、という事態が容易に起こり得るのです。

もし、自分の環境のデフォルトエンコーディングを確認したい場合は、SystemクラスのgetPropertyメソッドを利用することで取得できます。

System.out.println(System.getProperty("file.encoding"));

4. 文字コードを指定しよう

デフォルトエンコーディングの違いによる文字化けは、文字コードを任意に指定することによって防ぐことができます。

文字コードを明示する場合、特に理由がなければ「UTF-8」を指定するのが一般的です。
UTF-8を指定するメリットは、主に4つあります。

  • 国際化対応 : UTF-8は、UTF-16と同じく、世界中の文字を統一的に表す文字集合Unicodeを扱う文字コードの1つです。多種類の文字を扱えるため、複数の言語を処理するプログラムでも、単一のエンコーディングで取り扱えます

  • ASCIIとの互換性 : UTF-8の一部(0~127)は、ASCIIコードと完全に一致します。これによって、ASCII文字のみで構成されたファイルも、UTF-8で取り扱えます。多くのプログラミング言語やライブラリ、システムはASCIIをベースとして設計されてきたため、ASCIIとの互換性は重要です

  • 可変長エンコーディング : UTF-8は、1文字を1バイトから4バイトで表現する可変長エンコーディングです。ASCII文字(英数字や記号)は1バイトで表現できるため、それらを主体としたテキストでは、ファイルサイズを小さく抑えることができます

  • 高い普及率 : Webの標準エンコーディングとしてW3Cに推奨されており、多くのOS・プログラミング言語・データベース・ライブラリなどがデフォルトのエンコーディングとしてUTF-8をサポートしています

また、どのような時に文字コードの指定が必要かというと、ざっくり言えば「Javaの内部環境と外部環境の間を文字情報が行き来する場合」といえます。

具体的には、ファイルの読み書き・データベース・コンソール(キーボードなど)入出力など外部リソースとの情報のやりとりが挙げられます。特に、異なるOS間でファイルをやり取りする場合は、注意が必要でしょう。
また、プログラム内で文字列とバイト列間の変換を行う際も、文字コードを指定しなければ、デフォルトエンコーディングが利用されてしまうので注意しましょう。

一方で、外部リソースを使用せず、プログラム内で完結する処理であれば、通常は文字コードの指定を意識する必要はありません。
また、現代のWebアプリケーション開発(Spring Boot, Jakarta EEなど)では、フレームワークやライブラリが自動的にUTF-8として処理してくれるため、開発者が明示的に文字コードを指定する必要はほとんどありません。

記事冒頭のSpotBugs先生の「デフォルトエンコーディングに依存している」という警告は、「文字コードを指定しないと、デフォルトエンコーディングの違いで文字化けが発生する可能性があるけど、大丈夫かい?」という意味だったのです。

5. 文字コードの指定例

文字コードの具体的な指定方法を、いくつか見ていきましょう。

文字コードは文字列で指定することもできますが、タイプミスをするとUnsupportedEncodingExceptionという例外がスローされます。

そのため、現代では「StandardCharsets.UTF-8」といったCharset型の定数を利用する方法が一般的です。

1. ファイル書き込みでの文字コードの指定

OutputStreamWriterは、バイト出力ストリーム(FileOutputStream)に文字出力の機能を追加するクラスです。
OutputStreamWriterコンストラクタの第2引数に、Charset型の定数を指定します。

String content = "Hello World!";
String filePath = "example.txt";

//InputStreamReaderのコンストラクタにCharset型の定数を指定します
try (OutputStreamWriter writer = new OutputStreamWriter(
        new FileOutputStream(filePath), StandardCharsets.UTF_8)) {
    writer.write(content);
} catch (IOException e) {
    e.printStackTrace();
}

2. ファイル読み込みでの文字コードの指定

InputStreamReaderは、バイト入力ストリーム(FileInputStreamなど)に文字入力の機能を追加するクラスです。
InputStreamReaderのコンストラクタの第2引数に、Charset型の定数を指定します。

String filePath = "example.txt"; 

//InputStreamReaderのコンストラクタにCharset型の定数を指定します
try (InputStreamReader reader = new InputStreamReader(
        new FileInputStream(filePath), StandardCharsets.UTF_8)) {
    StringBuilder sb = new StringBuilder();
    int c;
    while ((c = reader.read()) != -1) {
        sb.append((char) c);
    }
    System.out.println(sb.toString());
} catch (IOException e) {
    e.printStackTrace();
}

3.バイト配列と文字列の変換例

プログラム内で文字列をバイト配列に変換(エンコード)したり、バイト配列を文字列に変換(デコード)する際に、文字コードを指定します。

// バイト配列 → 文字列
String text = new String(byteArray, StandardCharsets.UTF_8);

// 文字列 → バイト配列
byte[] byteArray = text.getBytes(StandardCharsets.UTF_8);

まとめ

  • Javaのプログラム内部では、文字はUTF-16で統一的に扱われます

  • ファイル入出力など、Javaの外部環境とデータをやり取りする際は、文字をバイト列に変換(エンコーディング)する必要があります

  • OS等に依存する「デフォルトエンコーディング」に頼ると文字化けの原因となるため、文字コードは明示的に指定することが重要です

  • 文字コードを指定する際は、互換性や普及率の高さからUTF-8を利用することが推奨されます


記事は以上です。
最後までお読みいただき、ありがとうございました。

参考情報一覧

この記事は以下の情報を参考にして執筆しました。

GitHubで編集を提案

Discussion