トップAndroid Java > ZIP圧縮および解凍

Android Java: ZIP圧縮および解凍

1.はじめに

自作地図アプリのバイナリレコードファイルはシンプルな4バイト境界なデータ形式としている。 そのため、日本地図全体では合計 4.5GBほどの大きさとなる。

ブロックに分割しているため、個々のファイルは最大で 50MB 程度であり、 メモリ使用量としては問題はなく、パフォーマンス上、シンプルなデータ形式が望ましい。

しかし、ファイルサイズが大きければ読み込み時間は大きくなる。 ファイルは zip形式として、読み込み処理で4バイト境界のシンプルなデータに展開する方が 総合的には速くなるであろう。

地図用バイナリレコードファイルはパソコンで作成しているため、パソコン側で zip圧縮して、 FTPでスマホに転送する。 現在は Windows のエクスプローラーで スマホに FTP転送しているが、 zip圧縮とFTP転送をプログラムで実行する方が操作性が向上する。

ただし、ZIP圧縮よりもFTP転送プログラムの方が大がかりになるようであれば、 FTP転送はこれまで通りの手作業とする。

2.単独ファイルのZIP圧縮

パソコンからスマホへの転送だけを考えれば、分割したファイルは zip で一まとめにした方がよいが、 スマホでの読み込みはパフォーマンス重視のため、多数のファイルを一まとめにした zip ファイルからの 読み込みではなく、個々のファイルを個別にzip圧縮したファイルからの読み込みとなる。

一まとめにする方法は記事[1]にあるので、ここでは省略する。

対象のファイル群は階層ディレクトリ下にある。この2階層をスキャンするために、 Files.walk(dirpath, 2) を使う。

以下のプログラムでバイナリレコードファイルを圧縮してみると、 試した例では平均的に半分以下のサイズになった。

一旦、バイナリファイルを出力してから圧縮するのではなく、 ファイル分割プログラムがファイルを出力する時点で zip圧縮して出力する方がよい。 しかし、分割ファイル数が多いため、同時オープンファイル数を抑えるために、時々クローズさせ、 追記モードで書き込みを行っているため、分割プログラムでダイレクトに zip出力するのは簡単ではない。

    static void encodeFiles(String dirSrc) {
        Path dirpath = Paths.get(dirSrc);
        try (Stream<Path> stream = Files.walk(dirpath, 2)) {
            stream.forEach(p -> encode(p.toString()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void encode(String strpath) {
        if (!strpath.endsWith(".dat")) {
            return;
        }
        byte[] buf = new byte[1024];
        String outPath = strpath.replace(".dat", ".zip");
        ZipOutputStream zos = null;
        try {
            zos = new ZipOutputStream(new FileOutputStream(outPath));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        try {
            Path path = Paths.get(strpath);
            String filename = path.getFileName().toString();
            InputStream is = new FileInputStream(strpath);
            ZipEntry ze = new ZipEntry(filename);
            zos.putNextEntry(ze);
            int len = 0;
            while ((len = is.read(buf)) != -1) {
                zos.write(buf, 0, len);
            }
            is.close();
            zos.closeEntry();
            zos.close();
            Files.delete(path);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }  
    }

3.ZIP展開

地図アプリではファイルに展開する必要はなく、直接 int配列に読み込めばよい。

[現在のプログラム]
    static int[] readAllInts(String file) throws IOException {
        File f = new File(file);
        int file_length = (int) f.length();
        int[] buffer = new int[file_length / 4];
        FileInputStream is = new FileInputStream(file);
        for (int offset = 0; offset < file_length; ) {
            int nbytes = is.read(ba);
            IntBuffer intBuf = ByteBuffer.wrap(ba).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
            intBuf.get(buffer, offset / 4, nbytes / 4);
            offset += nbytes;
        }
        is.close();
        return buffer;
    }
[ZIPファイルを読み込むプログラム]
    static int[] readZipAllInts(String file) throws IOException {
        ZipInputStream in = new ZipInputStream(new FileInputStream(file));
        ZipEntry zipEntry = in.getNextEntry();
        int file_length = (int)zipEntry.getSize();
        int[] buffer = new int[file_length / 4];
        for (int offset = 0; offset < file_length; ) {
            int nbytes = in.read(ba);
            IntBuffer intBuf = ByteBuffer.wrap(ba).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
            intBuf.get(buffer, offset / 4, nbytes / 4);
            offset += nbytes;
        }
        in.closeEntry();
        in.close();
        return buffer;
    }
    static int[] readZipAllInts(String file) throws IOException {
        ZipInputStream in = new ZipInputStream(new FileInputStream(file));
        ZipEntry zipEntry = in.getNextEntry();
        int file_length = (int)zipEntry.getSize();
        int[] buffer = new int[file_length / 4];
        for (int offset = 0; offset < file_length; ) {
            int nbytes = in.read(ba);
            IntBuffer intBuf = ByteBuffer.wrap(ba).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
            intBuf.get(buffer, offset / 4, nbytes / 4);
            offset += nbytes;
        }
        in.closeEntry();
        in.close();
        return buffer;
    }

getSize() が -1 となった。圧縮時に setSize() を実行してもうまくいかなかった。 byte[] ba を十分大きくしておき、一旦ファイル全体を読み込んでから int[] buffer に移す方法に変える。

    static int[] readZipAllInts(String file) throws IOException {
        ZipInputStream in = new ZipInputStream(new FileInputStream(file));
        ZipEntry zipEntry = in.getNextEntry();
        for (int offset = 0; offset < file_length; ) {
            int nbytes = in.read(ba, offset, ba.length - offset);
			if (nbytes < 0) break;
            offset += nbytes;
        }
        in.closeEntry();
        in.close();
        int[] buffer = new int[offset / 4];
        IntBuffer intBuf = ByteBuffer.wrap(ba).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
        intBuf.get(buffer);
        return buffer;
    }

プログラムは簡単であるが、ファイル読み込み時間の短縮は期待できないようである。

 #0 /storage/C060-A4CD/GIS/landsL2/3/1.dat 13ms
 lands 8/226/98 5recs 24ms
 #1 /storage/C060-A4CD/GIS/japanMz6/56/24.zip 554ms
 getOSMs japanMz/8/226/98 out=3086/3086 skip=1258 558ms
 lands 8/226/99 4recs 575ms
 lands 8/226/101 9recs 573ms
 Background concurrent copying GC freed 806298(51MB) AllocSpace objects, 127(107MB) LOS objects, 35% free, 173MB/269MB, paused 74us,18us total 117.021ms
 JNI critical lock held for 23.100ms on Thread[20,tid=5572,Runnable,Thread*=0xb400006f641770f0,peer=0x17800308,"Thread-14"]
 #2 /storage/C060-A4CD/GIS/japanMz6/56/25.zip 1266ms
 getOSMs japanMz/8/226/101 out=14939/14939 skip=12852 1278ms
 getOSMs japanMz/8/226/99 out=13443/13443 skip=6472 6ms
 lands 8/226/100 4recs 1866ms
 getOSMs japanMz/8/226/100 out=19872/19872 skip=7336 29ms
 #0 /storage/C060-A4CD/GIS/landsL2/3/1.dat 17ms
 lands 8/226/98 5recs 180ms
 lands 8/226/100 4recs 160ms
 lands 8/226/101 9recs 157ms
 #1 /storage/C060-A4CD/GIS/japanM6/56/24.dat 704ms
 getOSMs japanM/8/226/98 out=3086/3086 skip=1258 728ms
 lands 8/227/98 5recs 862ms
 #2 /storage/C060-A4CD/GIS/japanM6/56/25.dat 621ms
 getOSMs japanM/8/226/100 out=19872/19872 skip=7336 662ms

A.リファレンス

[1] ANDROIDでZIPファイルの圧縮・展開をする