Сжатие файла независимыми порциями, а затем объединение их в один действительный архив - PullRequest
0 голосов
/ 02 января 2019

Интересно, можно ли сжимать произвольный файл (или папку, или любую другую файловую структуру) независимыми порциями, а затем получать действительный архив (например, gzip), объединяя их вместе.Некоторые требования:

  • java 8
  • чанки <= 16 МБ </li>
  • структура папок не изменяется во время процесса
  • чанки сжимаются независимо, нопорядок сохраняется
  • каждый сжатый блок добавляется в конец результирующего архива
  • результирующий архив должен быть действительным и распакованным любым стандартным инструментом

Похожечтобы достичь этого, мне нужно сначала создать архивный заголовок, а затем просто добавить к нему сжатые блоки https://tools.ietf.org/html/rfc1952,, однако я не уверен, поддерживается ли он каким-либо из стандартных утилит java или сторонних библиотек.У кого-нибудь есть идеи, с чего начать?

Немного предыстории: у меня есть клиент-серверное приложение, которое позволяет пользователю загружать файлы в облачное хранилище.Связь через REST API, клиентская сторона будет отвечать за деление файлов на куски и загрузки их по одному.Можно выполнить сжатие в браузере, однако мне интересно, можем ли мы перенести эту нагрузку на серверную часть.

Ответы [ 2 ]

0 голосов
/ 04 января 2019

Да. Конкатенация файлов gzip является действительным файлом gzip в соответствии со стандартом (RFC 1952). gzip, безусловно, справится с этим.

Вы правы, опасаясь, что какой-то код может не поддерживать его, так как объединение членов gzip не очень распространено. Если вы хотите быть супер-безопасным, вы можете объединить файлы gzip в один элемент gzip без необходимости повторного сжатия. Однако вам необходимо прочитать все сжатые данные, эффективно распаковывая их в памяти (что все же намного быстрее, чем сжатие). Вы можете найти пример этого в gzjoin.c .

0 голосов
/ 03 января 2019

Вы можете попробовать что-то вроде этого для tar + gzip:

Зависимость Maven:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.18</version>
</dependency>

Java-код для сжатия в куски:

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

[..]

private static final int MAX_CHUNK_SIZE = 16000000;

public void compressTarGzChunks(String inputDirPath, String outputDirPath) throws Exception {
    PipedInputStream in = new PipedInputStream();
    final PipedOutputStream out = new PipedOutputStream(in);
    new Thread(() -> {
        try {
            int chunkIndex = 0;
            int n = 0;
            byte[] buffer = new byte[8192];
            do {
                String chunkFileName = String.format("archive-part%d.tar.gz", chunkIndex);
                try (OutputStream fOut = Files.newOutputStream(Paths.get(outputDirPath, chunkFileName));
                     BufferedOutputStream bOut = new BufferedOutputStream(fOut);
                     GzipCompressorOutputStream gzOut = new GzipCompressorOutputStream(bOut)) {

                    int currentChunkSize = 0;
                    if (chunkIndex > 0) {
                        gzOut.write(buffer, 0, n);
                        currentChunkSize += n;
                    }
                    while ((n = in.read(buffer)) != -1 && currentChunkSize + n < MAX_CHUNK_SIZE) {
                        gzOut.write(buffer, 0, n);
                        currentChunkSize += n;
                    }
                    chunkIndex++;
                }
            } while (n != -1);
            in.close();
        } catch (IOException e) {
            // logging and exception handling should go here
        }
    }).start();

    try (TarArchiveOutputStream tOut = new TarArchiveOutputStream(out)) {
        compressTar(tOut, inputDirPath, "");
    }
}

private static void compressTar(TarArchiveOutputStream tOut, String path, String base)
        throws IOException {
    File file = new File(path);
    String entryName = base + file.getName();
    TarArchiveEntry tarEntry = new TarArchiveEntry(file, entryName);
    tarEntry.setSize(file.length());
    tOut.putArchiveEntry(tarEntry);

    if (file.isFile()) {
        try (FileInputStream in = new FileInputStream(file)) {
            IOUtils.copy(in, tOut);
            tOut.closeArchiveEntry();
        }
    } else {
        tOut.closeArchiveEntry();
        File[] children = file.listFiles();
        if (children != null) {
            for (File child : children) {
                compressTar(tOut, child.getAbsolutePath(), entryName + "/");
            }
        }
    }
}

Java-кодобъединить фрагменты в один архив:

public void concatTarGzChunks(List<InputStream> sortedTarGzChunks, String outputFile) throws IOException {
    try {
        try (FileOutputStream fos = new FileOutputStream(outputFile)) {
            for (InputStream in : sortedTarGzChunks) {
                int len;
                byte[] buf = new byte[1024 * 1024];
                while ((len = in.read(buf)) != -1) {
                    fos.write(buf, 0, len);
                }
            }
        }
    } finally {
        sortedTarGzChunks.forEach(is -> {
            try {
                is.close();
            } catch (IOException e) {
                // logging and exception handling should go here
            }
        });
    }
}
...