はじめに
成熟した 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()
を使えば指定ディレクトリを再帰的にリストできる。
GzipCompressorOutputStream
と TarArchiveOutputStream
を 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 を解凍
GzipCompressorInputStream
と TarArchiveInputStream
を 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); } } } } } }