Java
zip
文字化け

JavaでWindows環境で文字化けしないZip圧縮を行う

More than 1 year has passed since last update.

JavaでファイルのZip圧縮を行う場合に、圧縮対象のファイル名に日本語文字が使われていると
一部のWindows環境で解凍時に文字化けが発生していたので、その回避方法を整理しました。

そもそも

Mac/Linuxで圧縮したファイルをWindowsで開いたりすると同様の問題が発生するケースがあります。
具体的にはWindows7 or WindowsServer2008R2でパッチが当たっていない場合に発生します。1

頼むから各種のパッチを当てて最新を使ってくれという気持ちになりますが
環境によっては容易に更新が行えないようなケースもあるため、その場合はプログラム側で対応しましょう。

実装

引数で指定されたファイル(またはディレクトリ)をpacked.zipというZipファイルに圧縮するサンプルです。

Java7からはZipOutputStreamのコンストラクタにCharsetを渡せるため
Charset.forName("Shift-JIS")を渡せばWindows環境でも文字化けしないZipファイルを作ることができます。

似た問題を探すとorg.apache.tools.zipパッケージを入れる必要があるという記事がいくつか見つかりますが
Java7からは標準ライブラリのみで実現できます。

ZipFile.java
package com.uskey512;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipFile implements AutoCloseable {

    private byte[] buffer;
    private List<File> targetFiles;
    private ZipOutputStream zipOutputStream;

    private static final int DEFAULT_BUFFER_SIZE = 1024;

    public ZipFile(String fileName, String path) throws IOException {
        this(fileName, path, Charset.defaultCharset(), DEFAULT_BUFFER_SIZE);
    }

    public ZipFile(String fileName, String path, int bufferSize) throws IOException {
        this(fileName, path, Charset.defaultCharset(), bufferSize);
    }

    public ZipFile(String fileName, String path, Charset charset) throws IOException {
        this(fileName, path, charset, DEFAULT_BUFFER_SIZE);
    }

    public ZipFile(String fileName, String path, Charset charset, int bufferSize) throws IOException {
        File zipFile = new File(path + File.separator + fileName);
        buffer = new byte[bufferSize];
        targetFiles = new ArrayList<File>();
        zipOutputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)), charset);
    }

    public void addFile(String filePath) {
        targetFiles.add(new File(filePath));
    }

    public void build() {
        writeFiles(targetFiles.toArray(new File[0]));
    }

    private void writeFiles(File[] files) {
        for (File file : files) {
            if (file.isDirectory()) {
                writeFiles(file.listFiles());
            } else {
                try {
                    ZipEntry entry = new ZipEntry(file.getPath().replace('\\', '/'));
                    zipOutputStream.putNextEntry(entry);
                    try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
                        int readLength = 0;
                        while (0 < (readLength = is.read(buffer))) {
                            zipOutputStream.write(buffer, 0, readLength);
                        }
                    }
                } catch (IOException ioe) {
                    throw new RuntimeException("", ioe);
                }
            }
        }
    }

    @Override
    public void close() throws IOException {
        zipOutputStream.close();
    }
}
Main.java
package com.uskey512;

import java.io.IOException;
import java.nio.charset.Charset;

public class Main {

    public static void main(String[] args) {
        // try (ZipFile zipFile = new ZipFile("packed.zip", ".") { // 化ける
        try (ZipFile zipFile = new ZipFile("packed.zip", ".", Charset.forName("Shift-JIS"))) { // 化けない
            zipFile.addFile(args[0]);
            zipFile.build();
        } catch (IOException ioe) {
        }
    }
}