[Java] Zipファイル内の日本語ファイル名


目的

Zipファイルは作られた環境によって日本語ファイル名の表現方法が異なるので整理する。
(主に、Javaでの扱い方)

環境ごとの違い

Zipファイルの日本語ファイル名は、一般的に以下のような文字コードで記載されている。

  • Windows : MS932
  • Linux : UTF-8(NFC)
  • Mac : UTF-8(NFD;一部独自)

文字コード

Java7 からは、java.util.zip.ZipFile で扱う際に文字コードを指定することができるようになった。
(Java6まではUTF-8固定だったので別のライブラリが必要だった。たとえばantとかcommonsとか)

ZipFile zipFile = new ZipFile(file, charset);

ただし、MS932とUTF-8のどちらで開けば良いか事前に判断することは難しい。

Unicode 正規化

NFC や NFD というのは、Unicodeの正規化方法の違いのこと。
ウムラウト記号や、濁点・半濁点をどのように表現するかが異なる。

  • NFC : 合成した1文字で表す(例:「ガ」を1文字で表す)
  • NFD : 結合文字で表す(例:「ガ」を「カ」+「結合文字の濁点」で表す)

参考: wikipedia:Unicode正規化

ちなみに結合文字の濁点(U+3099)と、単独の濁点(U+309b)は別のコードポイントになっている。
半濁点も同様。

int[] codePoints = { 0x3099, 0x309a, 0x309b, 0x309c };

for (int codePoint : codePoints) {
    System.out.printf("U+%04x : %s\n", codePoint, Character.getName(codePoint));
}
U+3099 : COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
U+309a : COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
U+309b : KATAKANA-HIRAGANA VOICED SOUND MARK
U+309c : KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK

NFC/NFDの変換は、java.text.Noralizer#normalize(CharSequence, Form)でできます。

/** NFDをNFCに変換 */
public String toNfc(String nfd) {
    return Normalizer.normalize(nfd, Normalizer.Form.NFC);
}

Javaからファイルを作る場合には、Mac環境であってもNFCで指定することができる(自動でNFDに変換して処理してくれる)。

逆にWindows上にNFDでファイルを作ると、見た目はNFCとほとんど同じだが別のファイルができてしまうので、注意が必要。

String nfcName = "がぎぐげご.txt";
// NFDに変換
String nfdName = Normalizer.normalize(nfcName, Normalizer.Form.NFD);

new File(nfcName).createNewFile(); // -> true
new File(nfdName).createNewFile(); // -> true (Windowsの場合)

そのため特別な理由がなければ、 UTF-8 の ZipEntry 名については NFC に変換してから使うほうが良いと思われる。