1. はじめに
想像してみてください。経理から受け取ったレポートのファイルが "Windows-1251"(キリル文字向けの古いエンコーディング)で保存されていました。あなたの Java アプリケーションは入力データをすべて "UTF-8" であることを前提にしているため、そうでないとエラーになったり、文字が「文字化け」してしまいます。あるいは、"ISO-8859-1" のみを受け付けるシステムと連携する場合もあります。どうすればよいでしょうか?もちろん、ファイルを再エンコードします。
再エンコード とは、あるエンコーディングでファイルからテキストを読み取り、別のエンコーディングで保存し直すプロセスです。受け手に確実に意図が伝わるよう、手紙を別の言語に書き換えるのに似ています。
Java における再エンコードの仕組み:基本的な考え方
Java の文字列(String)はプログラム内部では常に Unicode(UTF‑16)で保持されます。つまり、ファイルからテキストを読み込んだ時点でそれは「汎用的」になっており、サポートされている任意のエンコーディングで書き出せます。したがって再エンコードは次のようにシンプルです。
- 正しい元エンコーディングを指定して、ファイルを文字列として読み取る。
- 必要な出力エンコーディングを明示して、その文字列を新しいファイルに書き出す。
図:
[エンコーディング A のファイル] --(Charset A で読み取り)--> [メモリ上の String] --(Charset B で書き込み)--> [エンコーディング B のファイル]
2. 再エンコードの手順
ステップ 1. 入力(元)と出力(先)のエンコーディングを決める
入力側は元のファイルが保存されているエンコーディング(例:"Windows-1251")。出力側は結果として得たいエンコーディング(例:"UTF-8")です。
ステップ 2. 所定のエンコーディングで読み取り用ストリームを開く
Files.newBufferedReader(Path, Charset) または古典的な InputStreamReader を使います。
ステップ 3. 所定のエンコーディングで書き込み用ストリームを開く
Files.newBufferedWriter(Path, Charset) または古典的な OutputStreamWriter を使います。
ステップ 4. 行を読み、新しいファイルへ書き出す
行ごとに読み(ファイルが小さいなら全体読みでも可)、各行を書き出します。
ステップ 5. ストリームを閉じる(try-with-resources 推奨)
これによりリソースが確実に解放されます。try-with-resources 構文を使いましょう。
3. コード例:Windows-1251 → UTF-8 の再エンコード
Windows‑1251 から UTF‑8 へファイルを再エンコードする簡単なプログラムを実装してみましょう。特にロシア語データを扱うとき、現場でよく遭遇する例です。
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class FileReencoder {
public static void main(String[] args) throws IOException {
// ファイルのパス
Path inputPath = Paths.get("input-1251.txt");
Path outputPath = Paths.get("output-utf8.txt");
// 入力用のリーダーを Windows-1251(元のエンコーディング)で開く
try (
BufferedReader reader = Files.newBufferedReader(inputPath, Charset.forName("Windows-1251"));
BufferedWriter writer = Files.newBufferedWriter(outputPath, StandardCharsets.UTF_8)
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine(); // 改行を忘れずに!
}
}
System.out.println("ファイルは Windows-1251 から UTF-8 へ正常に再エンコードされました!");
}
}
ここでは、読み取り側には(Charset.forName("Windows-1251"))、書き込み側には(StandardCharsets.UTF_8)と、使用するエンコーディングを明示しています。さらに try-with-resources を使うことで、エラーが発生してもストリームが自動的に閉じられます。writer.newLine() は改行を出力し、元のファイル構造(行区切り)を保ちます。
4. 重要な注意点とヒント
元のファイルのエンコーディングを見極めるには?
- ファイル自体には通常、エンコーディングの「ラベル」はありません(例外は BOM ですが、常に付いているわけではありません)。
- 自分で作成したファイルなら、保存時と同じエンコーディングを使いましょう。
- どこかから届いたファイルなら、エンコーディングを表示できるエディター(例:Notepad++、Visual Studio Code)で開いてみてください。
- Linux では file -i 名前_ファイル コマンドを使えますが、常に正確とは限りません。
元のエンコーディングを間違えるとどうなる?
文字化けや文字の欠落が発生します。たとえば、Windows‑1251 で保存されたファイルを UTF‑8 として読んでしまうと、キリル文字が "mojibake" に化けます。
例外処理
ファイル操作では常にエラーの可能性があります:ファイルが見つからない、権限がない、不正なエンコーディングなど。例外処理(try-catch)を行うか(throws で)上位に送出して、アプリが「理由もなく」落ちないようにしましょう。
大きなファイルの処理
ファイルが非常に大きい(ギガバイト級)場合は、ここで示したように行単位で読み書きしましょう。ファイル全体を一度にメモリへ読み込むのは避けないと、OutOfMemoryError になります。
オンザフライの再エンコード(ストリーミング)
ファイルがとても大きい場合、ネットワークからのデータをストリーミング処理しながら「オンザフライ」で読み書きし、中間ファイルを作らない方法も取れます。
5. 再エンコードでありがちなミス
誤り №1: 元エンコーディングの取り違え。 元のエンコーディングを誤ると結果は悲惨です。「Privet」が "Привет" に化けます。ファイルがどのエンコーディングで作成されたかを必ず確認しましょう。
誤り №2: システム既定エンコーディングの暗黙利用。 エンコーディングを明示しないと、Java はシステム既定(System.getProperty("file.encoding"))を使います。これはマシンごとに異なるため(例:Windows は cp1251、Linux は UTF‑8)、結果が変わる可能性があります。
誤り №3: ファイル全体を一度に読み書きする。 大きなファイルではメモリ不足につながります。行単位の読み書きを使いましょう。
誤り №4: 例外を無視する。 ファイルが存在しない、他のプロセスに占有されている、壊れた文字が含まれるなどの可能性があります。常に例外を処理するか、try-with-resources を使いましょう。
誤り №5: バイナリファイルの再エンコード。 画像、PDF、アーカイブなどのバイナリファイルを再エンコードしてはいけません。破損します。再エンコードはテキストファイルに対してのみ意味があります。
GO TO FULL VERSION