忍者ブログ

軽Lab

 Javaを中心とした、プログラミング関係のナレッジベース

Home > > Java 圧縮・解凍パッケージを利用する(zip,gzip)

Java 圧縮・解凍パッケージを利用する(zip,gzip)

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

Home > > Java 圧縮・解凍パッケージを利用する(zip,gzip)

- ランダム記事 -
- PR -

コメント

ただいまコメントを受けつけておりません。

Home > Java > Java 圧縮・解凍パッケージを利用する(zip,gzip)

Java 圧縮・解凍パッケージを利用する(zip,gzip)

ユーザがアプリの保存データを扱う場面を思い浮かべると、できるだけ容量が小さく、できれば1つのファイルにまとまっている方が持ち運びなどに便利だろう。そう考えると、保存データを圧縮するという方法が有力な選択肢の1つとして考えられる。

というわけで、今回はJavaの標準ライブラリ(java.util.zip)を利用した圧縮・解凍方法を確認する。Javaの標準ライブラリではzip圧縮とgzip圧縮が利用可能となっている。それ以外の圧縮形式(7z、tar.gz(gzip+tar)など)に関しては外部ライブラリを使うと利用できるようになるが、こちらは別の記事で確認する。


■ Java標準ライブラリで利用可能な圧縮方式

 Javaにおいて圧縮・解凍を扱うクラスはjava.util.zipパッケージにまとめられている(*1)。java.util.zipパッケージで利用可能な圧縮方式は以下のとおりである。

圧縮形式 関連クラス 内容
DEFLATE / INFLATE Deflater
DeflaterInputStream
DeflaterOutputStream
Inflater
InflaterInputStream
InflaterOutputStream
インターネットで広く使われている圧縮アルゴリズム。zipやgzip内で利用されている。
GZIP GZIPInputStream
GZIPOutputStream
単一ファイルの圧縮・解凍を行う圧縮形式。複数ファイルを扱う際は、tar圧縮がよく併用される(「*.tar.gz」ファイル)。GZIPはGNU zipの略
ZIP ZipEntry
ZipFile
ZipInputStream
ZipOutputStream
Windowsで一般的な圧縮形式。複数ファイルの圧縮・解凍が可能

 また、java.util.zipパッケージには上記のほかにチェックサムを扱うAdler32クラスやCRC32クラスが存在し、チェックサムを利用して入出力するためのクラス(CheckedInputStream、ChedkedOutputStream)が存在する。

■ サンプル・プログラム1(zip圧縮・解凍)

 以下にJava標準ライブラリでzip圧縮・解凍するサンプルプログラムを示す。サンプルではフォルダを含む3つのファイルをzip圧縮・解凍している。

◇リソース
TestJava(プロジェクトフォルダ)
 ┣ src
 ┃ ┗ application
 ┃   ┗ TestCompress1.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 java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;

/**
 * 圧縮・解凍の利用
 * @author karura
 *
 */
public class TestCompress1
{
    /**
     * 起動用関数
     * @param args
     * @throws Exception 
     */
    public static void main( String[] args ) throws Exception
    {
        new TestCompress1();
    }
    
    /**
     * 処理メイン
     * @throws Exception
     */
    public TestCompress1() throws Exception
    {
        // 開始メッセージ
        System.out.println( "start!" );
        
        // ファイルの圧縮
        String      baseDir = "input";
        String[]    files   = { "file1.txt" , "file2.txt" , "sub1/file3.txt" , "sub2/"};
        
        // zip圧縮
        compressZip( baseDir , files , "output/zipfile.zip" );
        
        // zip解凍
        decompressZip( "output/zipfile.zip" , "output/zipfile/" );
        
        // 終了メッセージ
        System.out.println( "end!" );
    }
    
    /**
     * ファイルをzip圧縮する
     * @param inputBaseDir 入力ファイルを指定する際のベース・フォルダ・パス
     * @param inputFiles 圧縮するファイル名。inputBaseDirからの相対パスで指定
     * @param outputFile 作成するzipファイル名
     * @throws Exception
     */
    protected void compressZip( String inputBaseDir , String[] inputFiles , String outputFile ) throws Exception
    {
        // nullチェック
        if( inputBaseDir == null ){ return; }
        if( inputFiles   == null ){ return; }
        if( outputFile   == null ){ return; }
        
        // zipファイルの作成
        // try-with-resource構文でファイルcloseしている
        try(
            // 出力ファイル指定
            FileOutputStream        out         = new FileOutputStream( outputFile );
            BufferedOutputStream    bos         = new BufferedOutputStream( out );
            ZipOutputStream         archive     = new ZipOutputStream( out ) )
        {
            // 入力ファイルの数だけエントリーを追加
            for( String fileName : inputFiles )
            {
                // エントリー1つ分を出力開始
                ZipEntry entry   = new ZipEntry( fileName );
                archive.putNextEntry( entry );
                
                // フォルダの場合は次へ
                if( fileName.endsWith("/") )
                { 
                    archive.closeEntry();
                    continue;
                }
                
                // 入力ファイル指定
                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.closeEntry();
            }
        }
    }
    
    /**
     * zip解凍
     * @param inputFile 解凍するzipファイル
     * @param outputDir 解凍先フォルダ
     * @throws Exception 
     */
    protected void decompressZip( String inputFile , String outputDir ) throws Exception
    {
        // zipファイルの読込
        // try-with-resource構文でファイルcloseしている
        try(    FileInputStream         fis     = new FileInputStream( inputFile );
                ZipInputStream          archive = new ZipInputStream( fis ) )
        {
            // エントリーを1つずつファイル・フォルダに復元
            ZipEntry 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( ( size = archive.read( buf ) ) > 0 )
                    {
                        bos.write( buf , 0 , size );
                    }
                }
            }
        }
    }
    
}
◇実行結果
 ┗ output
   ┣ zipfile.zip
   ┗ zipfile
     ┣ file1.txt
     ┣ file2.txt
     ┣ sub1
     ┃ ┗ file3.txt
     ┗ sub2
   
◇解説
 zip圧縮はcompressZip関数(45行目)、zip解凍はdecompressZip関数(48行目)で実施している。compressZip関数の内部(59行目~105行目)では、ZipOutputStreamクラス(72行目)に対してエントリ(Zipファイル内のファイルのようなもの)を1つ1つ登録している(75行目~104行目)。エントリーの登録では、ヘッダ情報をZipEntryクラスとして登録(78行目~79行目)してから、ファイルの内容を登録している(89行目~99行目)。

 解凍は圧縮の逆で、zipファイル内のエントリーを取得(122行目)してから、ファイルを1つ1つ作成している(122行目~150行目)。

■ サンプル・プログラム2(ソケット通信の圧縮)

 以下にソケット通信を圧縮するサンプルプログラムを示す。サンプルでは圧縮した文字列を送るサーバ(TestCompress2Server)と受信・表示するクライアント(TestCompress2Client)の2つのプログラムが、localhost上のポートを経由して文字列を送受信する。

◇サンプルコード
package application;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.zip.GZIPOutputStream;

/**
 * 圧縮通信のサーバ
 * @author karura
 *
 */
public class TestCompress2Server
{
    /**
     * 起動用関数
     * @param args
     * @throws Exception 
     */
    public static void main( String[] args ) throws Exception
    {
        new TestCompress2Server();
    }
    
    /**
     * 処理メイン
     * @throws Exception
     */
    public TestCompress2Server() throws Exception
    {
        // 開始メッセージ
        final int   PORT    = 10000;
        System.out.println( "start");
        
        // ポート番号を指定して、ソケットを関連付ける
        // try-with-resource構文でcloseしている
        try( ServerSocket serverSocket    = new ServerSocket( PORT ) )
        {
            // ループ
            while( true )
            {
                // 接続を待ち受け
                System.out.println( "wait for " + PORT );
                Socket  socket          = serverSocket.accept();
                
                // 接続メッセージ
                System.out.println( "接続!" );
                
                
                // 圧縮通信をInputStreamとして開く
                // try-with-resource構文でcloseしている
                String  str = "Hello. 1234567890";
                GZIPOutputStream out = new GZIPOutputStream( socket.getOutputStream() );
                
                // メッセージを送信
                byte[]  buf = str.getBytes();
                out.write( buf , 0 , str.length() );
                
                // 更新
                out.finish();
                out.close();
                
                // ソケットを閉じる
                socket.close();
                
                // 終了メッセージ
                System.out.println( "end!" );
            }
        }
    }
}
package application;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.zip.GZIPOutputStream;

/**
 * 圧縮通信のサーバ
 * @author karura
 *
 */
public class TestCompress2Server
{
    /**
     * 起動用関数
     * @param args
     * @throws Exception 
     */
    public static void main( String[] args ) throws Exception
    {
        new TestCompress2Server();
    }
    
    /**
     * 処理メイン
     * @throws Exception
     */
    public TestCompress2Server() throws Exception
    {
        // 開始メッセージ
        final int   PORT    = 10000;
        System.out.println( "start");
        
        // ポート番号を指定して、ソケットを関連付ける
        // try-with-resource構文でcloseしている
        try( ServerSocket serverSocket    = new ServerSocket( PORT ) )
        {
            // ループ
            while( true )
            {
                // 接続を待ち受け
                System.out.println( "wait for " + PORT );
                Socket  socket          = serverSocket.accept();
                
                // 接続メッセージ
                System.out.println( "接続!" );
                
                
                // 圧縮通信をInputStreamとして開く
                // try-with-resource構文でcloseしている
                String  str = "Hello. 1234567890";
                GZIPOutputStream out = new GZIPOutputStream( socket.getOutputStream() );
                
                // メッセージを送信
                byte[]  buf = str.getBytes();
                out.write( buf , 0 , str.length() );
                
                // 更新
                out.finish();
                out.close();
                
                // ソケットを閉じる
                socket.close();
                
                // 終了メッセージ
                System.out.println( "end!" );
            }
        }
    }
}

package application;

import java.net.Socket;
import java.util.zip.GZIPInputStream;

/**
 * 圧縮通信のクライアント
 * @author karura
 *
 */
public class TestCompress2Client
{
    /**
     * 起動用関数
     * @param args
     * @throws Exception 
     */
    public static void main( String[] args ) throws Exception
    {
        new TestCompress2Client();
    }
    
    /**
     * 処理メイン
     * @throws Exception
     */
    public TestCompress2Client() throws Exception
    {
        // 開始メッセージ
        final int   PORT    = 10000;
        System.out.println( "start!" );
        
        // ソケットを作成
        try( Socket socket  = new Socket( "localhost" , PORT ) )
        {
            // サーバに文字を受信
            System.out.println( "接続!" );
            
            // 圧縮通信をInputStreamとして開く
            // try-with-resource構文でcloseしている
            try( GZIPInputStream   in  = new GZIPInputStream( socket.getInputStream() ) )
            {
                // 圧縮情報を受け取り、解凍・出力
                int     size    = 0;
                byte[]  buf     = new byte[ 1024 ];
                while( ( size = in.read( buf ) ) > 0 )
                {
                    // 解凍した情報を出力
                    System.out.println( size );
                    System.out.println( new String(buf,0,size) );
                }
            }
        }
        
        // 終了メッセージ
        System.out.println( "end!" );
    }
}

◇実行結果
start
wait for 10000
接続!
end!
wait for 10000
start!
接続!
17
Hello. 1234567890
end!
◇解説
 まず、出力結果の見方についての注意点を説明する。Eclipse上で2つのプログラムを起動すると以下の画面のように標準出力を表示するコンソールが片方のプログラム分しか見えなくなる。表示するコンソールを変更するには「選択されたコンソールの表示」ボタンを押下して、見たいコンソールを選択する必要がある。

図:「選択されたコンソールの表示」ボタンの位置

 次にプログラムを見ていく。GZIP圧縮では内部にエントリを持っていないので圧縮・解凍は非常に簡単で、GZIPOutputStream/GZIPInputStreamに対してwrite/read関数でデータを読書すればよいだけである(TestCompress2Server.java 52行目~63行目、TestCompress2Client 41行目~52行目)。なお、実際に利用するには適切な例外処理が必要となる(out.closeあたり)。

 ソケット通信のデータを圧縮する際の注意点としては、内部にエントリ情報(複数のファイル)を持っていないGZIPやDEFLATE形式で圧縮・解凍を利用する必要がある点があげられる(*2)。

■ 参照

  1. JavaDoc 「パッケージjava.util.zipの階層」
  2. stack overflow 「Problems with using ZipOutputStream and ObjectOutputStream」
  3. java2s.com 「Compressed socket : ServerSocket « Network Protocol « Java」
Home > Java > Java 圧縮・解凍パッケージを利用する(zip,gzip)

- ランダム記事 -
- PR -

コメント

プロフィール

管理者:
 連絡はContactよりお願いします。

PR