Java で .tar.gz の圧縮・解凍

はじめに

成熟した Commons Compress を使うのがベスト。

dependencies {
    implementation 'org.apache.commons:commons-compress:1.20'
}
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-compress</artifactId>
  <version>1.20</version>
</dependency>


対象ディレクトリ配下を .tar.gz

Java 1.8 で追加された Files.walk() を使えば指定ディレクトリを再帰的にリストできる。

GzipCompressorOutputStreamTarArchiveOutputStream を Decorator で繋げばよい。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;

public class App {

    public static void main(String[] args) throws Exception {

        Path dir = Paths.get("./var/logs");
        Path dest = Paths.get(dir.getFileName() + ".tar.gz");

        try (OutputStream fo = Files.newOutputStream(dest);
             OutputStream gzo = new GzipCompressorOutputStream(fo);
             ArchiveOutputStream out = new TarArchiveOutputStream(gzo);
             Stream<Path> stream = Files.walk(dir)) {

            stream.forEach(p -> {
                try {
                    ArchiveEntry entry = out.createArchiveEntry(
                            p.toFile(),
                            p.subpath(dir.getNameCount() - 1,
                                    p.getNameCount()
                            ).toString());
                    out.putArchiveEntry(entry);
                    if (p.toFile().isFile()) {
                        try (InputStream i = Files.newInputStream(p)) {
                            IOUtils.copy(i, out);
                        }
                    }
                    out.closeArchiveEntry();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            out.finish();
        }
    }
}

Stream API でのチェック例外の扱いは醜いので、以下のようにする方が良い。

for (Path p : (Iterable<Path>) stream::iterator) {
    ArchiveEntry entry = out.createArchiveEntry(
            p.toFile(),
            p.subpath(dir.getNameCount() - 1,
                    p.getNameCount()
            ).toString());
    out.putArchiveEntry(entry);
    if (p.toFile().isFile()) {
        try (InputStream i = Files.newInputStream(p)) {
            IOUtils.copy(i, out);
        }
    }
    out.closeArchiveEntry();
}

キャストが気持ち悪い場合は以下。

Iterable<Path> iterable = stream::iterator;
for (Path p : iterable) {
    // ...
}


.tar.gz を解凍

GzipCompressorInputStreamTarArchiveInputStream を Decorator で繋げばよい。

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.utils.IOUtils;

public class App {

    public static void main(String[] args) throws Exception {

        final Path dir = Paths.get("./var");
        final Path source = Paths.get("./logs.tar.gz");

        try (InputStream fi = Files.newInputStream(source);
             InputStream gzi = new GzipCompressorInputStream(fi);
             ArchiveInputStream in = new TarArchiveInputStream(gzi)) {

            ArchiveEntry entry;
            while ((entry = in.getNextEntry()) != null) {
                if (!in.canReadEntryData(entry)) {
                    continue;
                }

                File file = dir.resolve(entry.getName()).toFile();
                if (entry.isDirectory()) {
                    if (!file.isDirectory() && !file.mkdirs()) {
                        throw new IOException("failed to create directory " + file);
                    }
                } else {
                    File parent = file.getParentFile();
                    if (!parent.isDirectory() && !parent.mkdirs()) {
                        throw new IOException("failed to create directory " + parent);
                    }
                    try (OutputStream o = Files.newOutputStream(file.toPath())) {
                        IOUtils.copy(in, o);
                    }
                }
            }
        }
    }
}