Java Tips | I/O・ネットワーク:文字コード変換

Java Java
スポンサーリンク

業務で「文字コード変換ユーティリティ」が必要になる場面

業務システムでは、UTF-8 だけで世界が完結してくれれば楽ですが、現実はそうはいきません。 古い社内システムが Shift_JIS、外部ベンダーの CSV が EUC-JP、海外サービスとの連携が ISO-8859-1、メールヘッダが別のエンコーディング——文字コードの違いが、地味にバグとトラブルの温床になります。

「なんとなく new String(bytes) を使う」「getBytes() に文字コードを指定しない」 こういうコードが混ざると、環境によって文字化けしたり、本番だけおかしくなったりします。

だからこそ、「文字コード変換をきちんと意識して扱うユーティリティ」を用意しておくことが、 業務・実務ではかなり重要になります。

基本の考え方:「文字列」と「バイト列」をはっきり分ける

文字列は Java の世界、バイト列は外の世界

まず、頭の中で次のように整理してほしいです。

Java の String は「文字の列」であり、 ファイルやネットワークに出入りするときは「バイト列」に変換されます。

このときに必要なのが「文字コード(Charset)」です。

  • バイト列 → 文字列(読み込み)
  • 文字列 → バイト列(書き込み)

どちらの方向でも、「どの文字コードで変換するか」を必ず決める必要があります。

ここを曖昧にすると、「開発環境では動くけど本番で文字化けする」という、 一番やりたくないパターンにハマります。

文字コード変換ユーティリティの基本形

byte[] を別の文字コードとして解釈し直す

よくある業務パターンは、「ある文字コードで書かれたバイト列を、別の文字コードのバイト列に変換したい」というものです。 例えば、「Shift_JIS のファイルを UTF-8 に変換したい」などです。

その基本形はこうなります。

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class CharsetConvertUtils {

    public static byte[] convert(byte[] source,
                                 Charset from,
                                 Charset to) {
        String text = new String(source, from);
        return text.getBytes(to);
    }

    public static byte[] sjisToUtf8(byte[] source) {
        return convert(source, Charset.forName("Shift_JIS"), StandardCharsets.UTF_8);
    }

    public static byte[] utf8ToSjis(byte[] source) {
        return convert(source, StandardCharsets.UTF_8, Charset.forName("Shift_JIS"));
    }
}
Java

使い方の例です。

byte[] sjisBytes = Files.readAllBytes(Path.of("data/sjis.csv"));
byte[] utf8Bytes = CharsetConvertUtils.sjisToUtf8(sjisBytes);
Files.write(Path.of("data/utf8.csv"), utf8Bytes);
Java

ここで重要なのは、「必ず Charset を明示している」ことです。 new String(source)text.getBytes() のように文字コードを省略すると、 OS や JVM のデフォルトに依存してしまい、環境差の原因になります。

ファイルの文字コード変換ユーティリティ

入力ファイルをある文字コードとして読み、別の文字コードで書き出す

実務では、「ファイルを丸ごと別の文字コードに変換したい」という場面が多いので、 それ専用のユーティリティを用意しておくと便利です。

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;

public class FileCharsetConvertUtils {

    public static void convertFile(Path input,
                                   Path output,
                                   Charset from,
                                   Charset to) throws IOException {
        byte[] source = Files.readAllBytes(input);
        String text = new String(source, from);
        byte[] target = text.getBytes(to);
        Files.write(output, target);
    }
}
Java

使い方の例です。

Path sjisFile = Path.of("data/sjis.csv");
Path utf8File = Path.of("data/utf8.csv");

FileCharsetConvertUtils.convertFile(
        sjisFile,
        utf8File,
        Charset.forName("Shift_JIS"),
        StandardCharsets.UTF_8
);
Java

ここで深掘りしたいのは、「変換の中心は常に String である」ということです。 バイト列を直接別の文字コードに変換するのではなく、

  • まず「元の文字コード」で文字列にする
  • 次に「目的の文字コード」でバイト列にする

という二段階を踏みます。

この流れを意識しておくと、「どこで文字化けが起きているか」を追いやすくなります。

ストリームで文字コード変換する(大きなファイル向け)

InputStreamReader と OutputStreamWriter を使う

巨大なファイルでは readAllBytes() がメモリを圧迫するので、 「ストリームで少しずつ変換する」形が安全です。

そのときに使うのが InputStreamReaderOutputStreamWriter です。

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;

public class StreamCharsetConvertUtils {

    public static void convertFileStreaming(Path input,
                                            Path output,
                                            Charset from,
                                            Charset to) throws IOException {
        try (InputStream in = Files.newInputStream(input);
             Reader reader = new InputStreamReader(in, from);
             OutputStream out = Files.newOutputStream(output);
             Writer writer = new OutputStreamWriter(out, to)) {

            char[] buffer = new char[4096];
            int len;
            while ((len = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, len);
            }
        }
    }
}
Java

ここで重要なのは、「バイト列ではなく文字(char)単位で扱っている」ことです。 InputStreamReader が「バイト列 → 文字列」、 OutputStreamWriter が「文字列 → バイト列」を担当してくれます。

そして、ここでも「部分読み込みと部分書き込み」を正しく扱う必要があります。 reader.read(buffer) の戻り値(len)を必ず使い、 writer.write(buffer, 0, len) として「有効な範囲だけ」書き出します。

文字コード変換とセキュリティ・運用の視点

「どの文字コードを受け入れるか」を決めておく

文字コード変換は、単なる技術的な話に見えますが、 業務では「どの文字コードを受け入れるか」がセキュリティ・運用の設計になります。

例えば、

  • 外部からの CSV は UTF-8 のみ受け付ける
  • 古いシステムとの連携部分だけ Shift_JIS を許容する
  • ログはすべて UTF-8 に統一する

といった方針を決めておくことで、 「よく分からない文字コードのファイルが紛れ込む」リスクを減らせます。

ユーティリティ側で「許可された文字コードだけを扱う」「未知の文字コードはエラーにする」といった制御を入れておくと、 業務コードがシンプルになり、監査もしやすくなります。

文字化けは「静かに壊れる」のでログと検証が大事

文字コードの問題は、例外を投げずに「静かに文字化けする」ことが多いです。 そのため、次のような工夫が有効です。

  • 変換前後の文字数や行数をチェックする
  • 変換時に使った文字コードをログに残す
  • テストデータに「日本語・記号・絵文字など」を含めて検証する

文字コード変換ユーティリティを作るときは、 「失敗したときにどう気づくか」までセットで考えておくと、 本番での事故をかなり減らせます。

まとめ:文字コード変換ユーティリティで身につけてほしい感覚

文字コード変換ユーティリティは、「文字列とバイト列の橋渡しを、意識的に・安全に行う」ための道具です。 そこには、次のような大事な感覚が詰まっています。

Java の世界では String が中心であり、外の世界とは常に「文字コード付きの変換」でつながること。 new String(bytes, charset)text.getBytes(charset) をセットで使い、文字コードを必ず明示すること。 小さなファイルは readAllBytes+変換、大きなファイルはストリーム+InputStreamReaderOutputStreamWriter で扱うこと。 「どの文字コードを受け入れるか」を業務として決め、ユーティリティでそれを守ること。

もし今、あなたのプロジェクトに「文字コードを指定していない getBytes()new String()」が散らばっているなら、 それを一度「文字コード変換ユーティリティ+明示的な Charset」に置き換えられないか眺めてみてください。

そこから先は、文字化けと環境差に振り回されることが、ぐっと減っていきます。

タイトルとURLをコピーしました