【Java】zip圧縮について
javaでzip圧縮する方法のメモ。
ググれば出てくるけど、自分用のメモのために。
基本的には、
- 圧縮対象のファイルをもとにZipEntryをつくる
- ZipOutputStream#putNextEntryにZipEntryを格納する
- 対象ファイルをInputStream#readで全部読み込み、ZipOutputStream#writeに書き出す
- 以上1.~3.を対象ファイル分繰り返す
の流れ。
ただし、JDK1.6以前か1.7以後かで使うZipOutputStreamが少し変わる。
これは日本語名のファイルを扱う場合の考慮点によるものである。
Javaにおけるzipの扱いは、ファイル名を全てUTF-8で扱う関係上、
Windows環境下で作成した日本語名ファイル(MS932)をzipにすると文字化けしてしまう。
これに対する対応がJDKのバージョンによって多少変わるからである。
逆に言うと日本語名を含まない、ASCIIだけで構成されたファイル名なら
別になんの問題もなく(JDKのバージョンに寄らず)zip圧縮が可能である。
◆JDK1.6以前
import java.io.*; import org.apache.tools.zip.*; ・・・(略)・・・ /** * 引数で渡された配列内の全ファイルを *「test.zip」という名前のzipファイル名で圧縮します * @param inFiles 圧縮対象のファイル * @throws IOException 何らかの入出力処理例外が発生した場合 */ private static void toZipTest(File[] inFiles) throws IOException { ZipOutputStream zos = null; byte[] data = new byte[1]; try { zos = new ZipOutputStream(new FileOutputStream(new File("test.zip"))); // ←★ zos.setEncoding("MS932"); for (int i=0; i < inFiles.length; i++) { File inFile = inFiles[i]; ZipEntry ze = new ZipEntry(inFile.getName()); // ←1. zos.putNextEntry(ze); // ←2. InputStream is = new FileInputStream(inFile); while (is.available() != 0) { // ←3. is.read(data); zos.write(data,0,data.length); } is.close(); } } catch(IOException e) { throw e; } finally { if (zos != null) { zos.close(); } } }
◆JDK1.7以降
import java.io.*; import java.util.zip.*; import java.nio.charset.*; ・・・(略)・・・ /** * 引数で渡された配列内の全ファイルを *「test.zip」という名前のzipファイル名で圧縮します * @param inFiles 圧縮対象のファイル * @throws IOException 何らかの入出力処理例外が発生した場合 */ private static void toZipTest(File[] inFiles) throws IOException { ZipOutputStream zos = null; byte[] data = new byte[1]; try { zos = new ZipOutputStream(new FileOutputStream(new File("test.zip")),Charset.forName("MS932")); // ←★ for (int i=0; i < inFiles.length; i++) { File inFile = inFiles[i]; ZipEntry ze = new ZipEntry(inFile.getName()); // ←1. zos.putNextEntry(ze); // ←2. InputStream is = new FileInputStream(inFile); while (is.available() != 0) { // ←3. is.read(data); zos.write(data,0,data.length); } is.close(); } } catch(IOException e) { throw e; } finally { if (zos != null) { zos.close(); } } }
見ての通り、JDK1.6以前とJDK1.7以降では1.~3.までの実装は完全に同じ。
「★」の部分が少し違う。
JDK1.6以前ではjavaの標準パッケージを使わず、
Apache Commonsのライブラリを使う=ant.jarに含有されるorg.apache.tools.zip.ZipOutputStreamを使う。
Apacheの方のZipOutputStreamでは、ファイル名に関する文字コード指定が可能で、
それがZipOutputStream#setEncodingである。
これにより、日本語名ファイルを使っても文字化けしないようになっている。
JDK1.7以降だと、javaの標準ライブラリでCharset指定が可能となったため、
使うZipOutputStreamもjava.util.zip.ZipOutputStreamで事足りるようになった。
標準ライブラリではJDK1.7以降、コンストラクタの引数にCharsetを引き渡すことで、
扱うファイル名の文字コードを指定することが可能となり、
結果Apacheのライブラリと同じように文字化けを回避できるようになっている。
いちいちant.jarをクラスパスに通さなきゃいけないことを考えると、
できるならJDK1.7以降のやり方の方がわかりやすくていい。
ただ昔のシステムだったりするとそうもいかないだろう。
かといって刷新とかの大きなタイミングでもなければJavaのバージョンあげたところでこんなところも直すまい。
メールといい、日本語文字文化圏はどーもこの辺の対応を後回しにされており悲しい。
まあ一番は日本語のファイルなんかシステムに扱わせるな!ってことですな。
ちなみに、↑のメソッドの引数のFileの配列だが、
これにディレクトリを含めるとIOExceptionが発生して落ちる。
(まあそりゃそうだよね)
なのでこのメソッドを呼び出す段階では
Fileの中身は本当に全部Fileのみ(全ての要素がFile#isFileでtrueを返す)にしておく必要がある。
まあFile[]の中身ループするところで、File#isDirectory使って判定し、
ディレクトリならFile#listFilesで配下のFile取得して再帰で自分をもっかい呼び出せば事足りるが。
こんなかんじ。↓
private static void toZipTest(File[] inFiles) throws IOException { ZipOutputStream zos = null; byte[] data = new byte[1]; try { zos = new ZipOutputStream(new FileOutputStream(new File("test.zip")),Charset.forName("MS932")); for (int i=0; i < inFiles.length; i++) { File inFile = inFiles[i]; if (inFile.isDirectory()) { toZipTest(inFile.listFiles()); } else { // 以下はJDK1.7以降の実装と同じ ZipEntry ze = new ZipEntry(inFile.getName()); zos.putNextEntry(ze); InputStream is = new FileInputStream(inFile); while (is.available() != 0) { is.read(data); zos.write(data,0,data.length); } is.close(); } } } catch(IOException e) { throw e; } finally { if (zos != null) { zos.close(); } } }