「ZipInputStream」でWIndows環境で作成した日本語ディレクトリを読み込む方法

「ZipInputStream」を使ってzipファイル読み込み処理の実装を頑張っています。
すると、デバッグ中に特定のファイルだけ何故か読み込むことが出来ない現象にぶち当たりました。そのファイルとは・・・

「Windows環境で作成した日本語名ディレクトリのファイル」

普段からMacを使っていると中々気付きにくい点で厄介でした。
このファイルをどうにかして読み込ませたい・・・。

要件は

  • 英数と日本語名で渡されるどちらのケースにも対応。
  • ディレクトリ名自体は後の処理で使用しない。
  • とりあえずディレクトリ内のファイルを読み込む事が出来ればOK。

調べてみると、「ZipInputStream」で文字コードを指定して対応させることで対処出来たのでメモとして書いていきます。

スポンサーリンク

環境情報

  • java version “1.8.0_172”
  • Kotlin version 1.2.50-release-103

原因はZipInputStream

Windowsで作成したマルチバイト名のディレクトリだけ読み込みません。
早速読み込み失敗時のエラーを見てみます。

java.lang.IllegalArgumentException: MALFORMED
    at java.util.zip.ZipCoder.toString(ZipCoder.java:58) ~[na:1.8.0_172]
    at java.util.zip.ZipInputStream.readLOC(ZipInputStream.java:300) ~[na:1.8.0_172]
    at java.util.zip.ZipInputStream.getNextEntry(ZipInputStream.java:122) ~[na:1.8.0_172]

// 以下略

「MALFORMED」とあります。
ググってみると、「奇形の」「不正な形式の」とあります。
どうやら渡したファイルが不正なデータ扱いされているようです。

早速、実装コードの中を確認してみます。

実装を見てみる。

問題の実装はこんな感じ。
it.nextEntryを呼んだ所で例外が発生しています。

ZipInputStream(file.inputStream).use { input ->
    val entry = input.nextEntry ?: break
// 略

では、次に「ZipInputStream」の中を見てみます。

public ZipInputStream(InputStream in) {
    this(in, StandardCharsets.UTF_8);
// 略

原因はここでした。
どうやら何も指定しないとデフォルトの文字コードは「UTF-8」になっています。

ここに「UTF-8」以外の文字コード名のファイルを渡しても、当然予期していない文字コードなので読み込むことが出来ずエラーを吐きます。

ちなみにWindows環境で作成したディレクトリは「MS932」という文字コードになります。

「Windows = Shift-JIS」だと思っていましたが、そうではないらしいです。
「MS932」は「Windows-31J」という名称とも同義で、Javaの世界では「MS932」を使用して区別しているそうです。

Shift_JIS と Windows-31J (MS932) の違いを整理してみよう / WEB ARCH LABO

解決コード

原因は

  • 「MS932」の文字コードのディレクトリを「UTF-8」として読み込もうとしていたから

ということで、こういうコードになりました。

val CHARSET = listOf("UTF-8", "MS932").map(::charset)

CHARSET.forEach {
    try {
        ZipInputStream(file.inputStream, it).use { input ->
            val entry = input.nextEntry ?: break
            // 略
        }
        return
    } catch (e: IllegalArgumentException) {
        // ログ出力
    }
}

定数で 定義した「文字コードのリスト」をforEachで回しています。
.mapStringからCharsetに変換しています。

try-catchで失敗した場合はログを出力し、ループ自体は続行。
成功した場合はreturnでループを離脱。

これでUTF-8で読み込みが失敗した場合、MS932を用いて読み込みを行うことが出来ました。

おわり!

参考サイト

Java Zipファイルメモ(Hishidama’s java zip Memo) – Ne

ZipInputStream(InputStream, Charset) decodes ZipEntry file name falsely / stackoverflow

Android Nougat以降で日本語フォルダ名を含むZipFile / なーんだ、ただの水たまりじゃないか

Shift_JIS と Windows-31J (MS932) の違いを整理してみよう / WEB ARCH LABO