今回は、Javaで7zやtar.gzなどJava標準ライブラリ上で扱えない圧縮形式を扱うライブラリApache Commons Comperssの利用方法を確認していく。
■ Apache Commons Compressライブラリについて
Apache Commons Compressライブラリは7zを始めとする主要な圧縮形式で圧縮・解凍を行えるようになるライブラリ。ライセンスはAPACHE LICENSE 2なので商用利用も可能である。Apache Commons Compressで対応している圧縮形式は以下の通り。
表:対応フォーマット一覧(内容は主にwikipedia情報)
圧縮形式 |
内容 |
ar |
Unixで利用されるアーカイバ。16バイト以上のファイル名が使えない制約がある。一部システムで利用されるが、現在はtarの利用が主流である |
arj |
圧縮・解凍ソフトARJで圧縮されたファイル。arとは無関係な模様 |
cpio |
Unixで利用されるアーカイバ。一部システムで利用されるが、現在はtarの利用が主流である。 |
Z |
Unix compressコマンドで圧縮されたファイル。gzipのほうが圧倒的に圧縮率が高く、ライセンスの問題もあるため商用Unix以外での利用はあまりない。tarとともに利用して「*.tar.Z」「*.taz」という拡張子をつけることがある。 |
dump |
Unixでバックアップに利用される圧縮形式? |
tar |
Unixで利用されるアーカイバ。複数ファイルを1つにまとめるだけのフォーマットであり、gzip圧縮と併用して「*.tar.gz」形式で利用するのが一般的である。 |
DEFLATE |
データ圧縮アルゴリズム。zipやgzipで利用されている。 |
zip |
Windowsで利用される圧縮形式。Java標準パッケージ(java.util.zip)でも利用可能 |
gzip |
Unixで利用される圧縮形式。アーカイブ機能はない。Java標準パッケージ(java.util.zip)でも利用可能 |
jar |
Javaのバイトコードやリソースがzip形式で圧縮されたファイル |
Pack200 |
Java Web Startアプリのために作成された圧縮形式。数MB程度の大きさのJarファイルにおいて、内部にclassファイルしか持たない場合は1/9程度のサイズまで圧縮できる。JDK付属のpack200コマンドにより圧縮できる |
bzip2 |
gzipやzipよりも高い圧縮率を誇る圧縮形式。処理速度はgzipよりも遅く7zよりも早いとのこと。拡張子は「.bz2」 |
LZMA |
データ圧縮アルゴリズム。7zやxzで利用されている。
*「xz for Java」ライブラリが追加で必要 |
XZ |
gzipやbzip2よりも高い圧縮率を誇る圧縮形式。処理速度はgzipより多少遅く、bzip2より速いとのこと。アーカイブ機能はない。
*「xz for Java」ライブラリが追加で必要 |
7z |
zipよりも高い圧縮率を持つ圧縮形式。様々な圧縮アルゴリズムに対応可能なことに加え、AES-256による暗号化も可能。
*「xz for Java」ライブラリが追加で必要 |
Snappy |
圧縮率は低いが、処理速度が極めて速い圧縮形式。 |
利用方法
Apache Commons Compressライブラリは以下のwebサイトでダウンロード可能である。必要なファイルは「commons-compress-○○.jar」であり、利用するプロジェクトのクラスパスに追加する必要がある(クラスパスの追加方法は
コチラ)。版番号の遷移が分かりづらいが、最新版は1.11と思われる。
xzライブラリのコンパイル
xzと7zを利用する場合には、上記ライブラリに加えて「xz for Java」ライブラリにもクラスパスを通す必要がある。「xz for Java」のソースは以下のwebサイトからダウンロード可能である。最新バージョンは1.5である。ライセンスはpubic domainである。
ソースをコンパイルするにはAntを利用する。コンパイルの方法は以下のとおりである。
1.eclipse上で新規Javaプロジェクトを作成、ダウンロードしたソースを上書きコピーする。
2.「build.xml」を右クリックして「実行 - Antビルド」を実行
3.ビルドに成功すると新たに「build」フォルダが作成され、「build/jar/xz.jar」にxzライブラリが出力される。
■ サンプル・プログラム1(7z圧縮・解凍)
以下にApache Commons Compressライブラリを利用して、7z圧縮と解凍を行うサンプル・プログラムを示す。サンプルではinputフォルダ以下のファイル群をoutput/7zfile.7zに圧縮したのち、output/7zfile.7zの内容をoutput/7zfileフォルダ以下に解凍する。
◇リソース
TestJava(プロジェクトフォルダ)
┣ src
┃ ┗ application
┃ ┗ TestCompress7Z.java
┣ input
┃ ┣ file1.txt
┃ ┣ file2.txt
┃ ┣ sub1
┃ ┃ ┗ file3.txt
┃ ┗ sub2
┗ output
◇サンプルコード
package application;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
/**
* 圧縮・解凍ライブラリの利用
* Apache Commons Compressライブラリ
* @author karura
*
*/
public class TestCompress7Z
{
/**
* 起動用関数
* @param args
* @throws Exception
*/
public static void main( String[] args ) throws Exception
{
new TestCompress7Z();
}
/**
* 処理メイン
* @throws Exception
*/
public TestCompress7Z() throws Exception
{
// 開始メッセージ
System.out.println( "start!" );
// ファイルの圧縮
String baseDir = "input";
String[] files = { "file1.txt" , "file2.txt" , "sub1/file3.txt" , "sub2/"};
// 7z圧縮
compress7Z( baseDir , files , "output/7zfile.7z" );
// 7z解凍
decompress7Z( "output/7zfile.7z" , "output/7zfile/" );
// 終了メッセージ
System.out.println( "end!" );
}
/**
* ファイルを7z圧縮する
* @param inputBaseDir 入力ファイルを指定する際のベース・フォルダ・パス
* @param inputFiles 圧縮するファイル名。inputBaseDirからの相対パスで指定
* @param outputFile 作成する7zファイル名
* @throws Exception
*/
protected void compress7Z( String inputBaseDir , String[] inputFiles , String outputFile ) throws Exception
{
// 7zファイルの作成
// try-with-resource構文でファイルcloseしている
try(
// 出力ファイル指定
SevenZOutputFile archive = new SevenZOutputFile( new File( outputFile ) ) )
{
// 入力ファイルの数だけエントリーを追加
for( String fileName : inputFiles )
{
// エントリー1つ分を出力開始
File file = new File( fileName );
// フォルダの場合は次へ
if( fileName.endsWith("/") )
{
SevenZArchiveEntry entry = archive.createArchiveEntry( file , fileName );
archive.putArchiveEntry( entry );
entry.setDirectory(true);
archive.closeArchiveEntry();
continue;
}
// 入力ファイル指定
SevenZArchiveEntry entry = archive.createArchiveEntry( file , fileName );
archive.putArchiveEntry( entry );
try( FileInputStream fis = new FileInputStream( inputBaseDir + "/" + fileName );
BufferedInputStream bis = new BufferedInputStream( fis ))
{
// エントリーの中身を出力
int size = 0;
byte[] buf = new byte[ 1024 ];
while( ( size = bis.read( buf ) ) > 0 )
{
archive.write( buf , 0 , size );
}
}
// エントリー1つ文の出力を終了
archive.closeArchiveEntry();
}
}
}
/**
* 7z解凍
* @param inputFile 解凍するzipファイル
* @param outputDir 解凍先フォルダ
* @throws Exception
*/
protected void decompress7Z( String inputFile , String outputDir ) throws Exception
{
// 出力先フォルダの作成
new File( outputDir ).mkdirs();
// 7zファイルの読込
// try-with-resource構文でファイルcloseしている
try( SevenZFile archive = new SevenZFile( new File( inputFile ) ) )
{
// エントリーを1つずつファイル・フォルダに復元
ArchiveEntry entry = null;
while( ( entry = archive.getNextEntry() ) != null )
{
// ファイルを作成
File file = new File( outputDir + "/" + entry.getName() );
// フォルダ・エントリの場合はフォルダを作成して次へ
if( entry.isDirectory() )
{
file.mkdirs();
continue;
}
// ファイル出力する場合、
// フォルダが存在しない場合は事前にフォルダ作成
if( !file.getParentFile().exists() ){ file.getParentFile().mkdirs(); }
// ファイル出力
try( FileOutputStream fos = new FileOutputStream( file ) ;
BufferedOutputStream bos = new BufferedOutputStream( fos ) )
{
// エントリの出力
int size = 0;
byte[] buf = new byte[ 1024 ];
while( archive.read( buf ) > 0 )
{
bos.write( buf , 0 , size );
}
}
}
}
}
}
◇実行結果
┗ output
┣ 7zfile.7z
┗ 7zfile
┣ file1.txt
┣ file2.txt
┣ sub1
┃ ┗ file3.txt
┗ sub2
◇解説
Apache Commons Compressライブラリを使った圧縮・解凍は、Java標準ライブラリ(java.util.zip)でzip圧縮・解凍を行う方法(詳細は
別記事で紹介)と大きな差異はない。7z圧縮はcompress7Z関数(46行目)、7z解凍はdecompress7Z関数(49行目)で行っている。
compress7Z関数内(62行目~105行目)の流れを見ると、最初にSevenZOutputFileクラスで出力ファイルを開き(68行目)、7zアーカイブに追加するエントリ(7z圧縮ファイル内のファイルのようなもの)を1つ1つ登録している(71行目~103行目)。エントリの登録はヘッダ情報であるSevenZArchiveEntryクラスを出力した後、エントリーの本文(ファイルの中身)を出力するという流れになる。
decompress7Z関数(113行目~154行目)の内容はcompress7Z関数で行った処理の逆を行っている。
■ サンプル・プログラム2(tar.gz圧縮・解凍)
以下にApache Commons Compressライブラリを利用して、tar.gz圧縮と解凍を行うサンプル・プログラムを示す。サンプルではinputフォルダ以下のファイル群をoutput/targzfile.tar→output/targzfile.tar.gzの順で圧縮した後、output/targzfile.tar.gzの内容をoutput/targzfile.tar→output/targzfileフォルダの順に解凍する。
◇リソース
TestJava(プロジェクトフォルダ)
┣ src
┃ ┗ application
┃ ┗ TestCompressTarGz.java
┣ input
┃ ┣ file1.txt
┃ ┣ file2.txt
┃ ┣ sub1
┃ ┃ ┗ file3.txt
┃ ┗ sub2
┗ output
◇サンプルコード
package application;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
/**
* 圧縮・解凍ライブラリの利用
* Apache Commons Compressライブラリ
* @author karura
*
*/
public class TestCompressTarGz
{
/**
* 起動用関数
* @param args
* @throws Exception
*/
public static void main( String[] args ) throws Exception
{
new TestCompressTarGz();
}
/**
* 処理メイン
* @throws Exception
*/
public TestCompressTarGz() throws Exception
{
// 開始メッセージ
System.out.println( "start!" );
// ファイルの圧縮
String baseDir = "input";
String[] files = { "file1.txt" , "file2.txt" , "sub1/file3.txt" , "sub2/"};
// tar圧縮
compressTarGz( baseDir , files , "output/targzfile.tar.gz" );
// tar解凍
decompressTarGz( "output/targzfile.tar.gz" , "output/targzfile/" );
// 終了メッセージ
System.out.println( "end!" );
}
/**
* ファイルを*.tar.gz圧縮する
* @param inputBaseDir 入力ファイルを指定する際のベース・フォルダ・パス
* @param inputFiles 圧縮するファイル名。inputBaseDirからの相対パスで指定
* @param outputFile 作成する*.tar.gzファイル名
* @throws Exception
*/
protected void compressTarGz( String inputBaseDir , String[] inputFiles , String outputFile ) throws Exception
{
// tarファイル名を作成
String outputTarFile = outputFile.replace( ".tar.gz" , ".tar" );
// tarアーカイブ作成
try( FileOutputStream fos = new FileOutputStream( outputTarFile );
TarArchiveOutputStream taos = new TarArchiveOutputStream( fos ) )
{
// 入力ファイルの数だけエントリーを追加
for( String fileName : inputFiles )
{
// フォルダの場合は次へ
if( fileName.endsWith("/") )
{
File file = new File( inputBaseDir + "/" + fileName );
TarArchiveEntry entry = new TarArchiveEntry( file , fileName );
taos.putArchiveEntry( entry );
taos.closeArchiveEntry();
continue;
}
// 入力ファイル指定
File file = new File( inputBaseDir + "/" + fileName );
TarArchiveEntry entry = new TarArchiveEntry( file , fileName );
taos.setLongFileMode( TarArchiveOutputStream.LONGFILE_POSIX );
taos.putArchiveEntry( entry );
try( FileInputStream fis = new FileInputStream( inputBaseDir + "/" + fileName );
BufferedInputStream bis = new BufferedInputStream( fis ))
{
// エントリーの中身を出力
int size = 0;
byte[] buf = new byte[ 1024 ];
while( ( size = bis.read( buf ) ) > 0 )
{
taos.write( buf , 0 , size );
}
}
// エントリー1つ文の出力を終了
taos.closeArchiveEntry();
}
}
// gzip圧縮
try( FileInputStream fis = new FileInputStream( outputTarFile );
BufferedInputStream bis = new BufferedInputStream( fis );
FileOutputStream fos = new FileOutputStream( outputFile );
GzipCompressorOutputStream archive = new GzipCompressorOutputStream(fos) )
{
// エントリーの中身を出力
int size = 0;
byte[] buf = new byte[ 1024 ];
while( ( size = bis.read( buf ) ) > 0 )
{
archive.write( buf , 0 , size );
}
}
}
/**
* *.tar.gz解凍
* @param inputFile 解凍する*.tar.gzファイル
* @param outputDir 解凍先フォルダ
* @throws Exception
*/
protected void decompressTarGz( String inputFile , String outputDir ) throws Exception
{
// 出力先フォルダの作成
new File( outputDir ).mkdirs();
// tarファイル名を作成
String outputTarFile = inputFile.substring( inputFile.lastIndexOf("/" ) ).replace( ".tar.gz" , ".tar.tmp" );
String outputTarDir = (outputDir.endsWith("/"))? outputDir.substring( 0 , outputDir.lastIndexOf( "/" ) )
: outputDir;
outputTarDir = outputTarDir.substring( 0 , outputTarDir.lastIndexOf( "/" ) );
String outputTarPath = outputTarDir + "/" + outputTarFile;
// gzip解凍
// try-with-resource構文でファイルcloseしている
try( FileInputStream fis = new FileInputStream( inputFile );
GzipCompressorInputStream archive = new GzipCompressorInputStream( fis );
FileOutputStream fos = new FileOutputStream( outputTarPath ) )
{
// エントリーの中身を出力
int size = 0;
byte[] buf = new byte[ 1024 ];
while( ( size = archive.read( buf ) ) > 0 )
{
fos.write( buf , 0 , size );
}
}
// tarアーカイブ解除
// try-with-resource構文でファイルcloseしている
try( FileInputStream fis = new FileInputStream( outputTarPath );
TarArchiveInputStream tais = new TarArchiveInputStream( fis ) )
{
// エントリーを1つずつファイル・フォルダに復元
ArchiveEntry entry = null;
while( ( entry = tais.getNextEntry() ) != null )
{
// ファイルを作成
File file = new File( outputDir + "/" + entry.getName() );
// フォルダ・エントリの場合はフォルダを作成して次へ
if( entry.isDirectory() )
{
file.mkdirs();
continue;
}
// ファイル出力する場合、
// フォルダが存在しない場合は事前にフォルダ作成
if( !file.getParentFile().exists() ){ file.getParentFile().mkdirs(); }
// ファイル出力
try( FileOutputStream fos = new FileOutputStream( file ) ;
BufferedOutputStream bos = new BufferedOutputStream( fos ) )
{
// エントリの出力
int size = 0;
byte[] buf = new byte[ 1024 ];
while( tais.read( buf ) > 0 )
{
bos.write( buf , 0 , size );
}
}
}
}
}
}
◇実行結果
┗ output
┣ targzfile.tar
┣ targzfile.tar.gz
┣ targzfile.tar.tmp
┗ targzfile
┣ file1.txt
┣ file2.txt
┣ sub1
┃ ┗ file3.txt
┗ sub2
◇解説
tar.gz圧縮・解凍と7z圧縮・解凍の大きな違いは、tar.gz圧縮・解凍が2段階の処理を行う点である。これはtarがアーカイブ機能(複数ファイルを1ファイルにまとめる)しか持たず、gzipが1ファイルに対する圧縮・解凍機能しか持たないことに由来する。
tarアーカイブ処理はzipや7zの圧縮処理と同様の処理と流れとなり、TarArchiveOutputStreamに対してエントリのヘッダ情報(TarArchiveEntryクラス)とエントリの本文を登録していくという流れになる(70行目~106行目)。tarアーカイブ解除については、その逆の処理である(159行目~193行目)。
gz圧縮・解凍は簡単で、GzipCompressorOutputStreamクラスおよびGzipCompressorInputStreamクラスでファイルを入出力するだけでよい(108行目~121行目、142行目~155行目)。
■ 参照
- Apache Commons Compress 「Examples」
- Apache Commons Compress 1.11 API
- programcreek 「Java Code Examples for org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream」