Java zip файлы из потоков мгновенно без использования byte [] - PullRequest
0 голосов
/ 21 декабря 2018

Я хочу сжать несколько файлов в zip-файлы, я имею дело с большими файлами, а затем загрузить их в клиент, пока я использую это:

@RequestMapping(value = "/download", method = RequestMethod.GET, produces = "application/zip")
public ResponseEntity <StreamingResponseBody> getFile() throws Exception {
    File zippedFile = new File("test.zip");
    FileOutputStream fos = new FileOutputStream(zippedFile);
    ZipOutputStream zos = new ZipOutputStream(fos);
    InputStream[] streams = getStreamsFromAzure();
    for (InputStream stream: streams) {
        addToZipFile(zos, stream);
    }
    final InputStream fecFile = new FileInputStream(zippedFile);
    Long fileLength = zippedFile.length();
    StreamingResponseBody stream = outputStream - >
        readAndWrite(fecFile, outputStream);

    return ResponseEntity.ok()
        .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION)
        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + "download.zip")
        .contentLength(fileLength)
        .contentType(MediaType.parseMediaType("application/zip"))
        .body(stream);
}

private void addToZipFile(ZipOutputStream zos, InputStream fis) throws IOException {
    ZipEntry zipEntry = new ZipEntry(generateFileName());
    zos.putNextEntry(zipEntry);
    byte[] bytes = new byte[1024];
    int length;
    while ((length = fis.read(bytes)) >= 0) {
        zos.write(bytes, 0, length);
    }
    zos.closeEntry();
    fis.close();
}

Это займетмного времени, прежде чем все файлы будут заархивированы, а затем начнется загрузка, а для больших файлов это может занять много времени, это строка, ответственная за задержку:

while ((length = fis.read(bytes)) >= 0) {
    zos.write(bytes, 0, length);
}

Так есть ли способзагружать файлы сразу, пока они заархивированы?

1 Ответ

0 голосов
/ 21 декабря 2018

Попробуйте вместо этого.Вместо того, чтобы использовать ZipOutputStream, чтобы обернуть FileOutputStream, записать свой zip-файл в файл, а затем скопировать его в поток вывода клиента, вместо этого просто используйте ZipOutputStream, чтобы обернуть поток вывода клиента так, чтобы при добавлении записей zipи данные он идет непосредственно к клиенту.Если вы хотите также сохранить его в файл на сервере, тогда вы можете сделать ZipOutputStream запись в разделенный поток вывода, чтобы записать оба местоположения одновременно.

@RequestMapping(value = "/download", method = RequestMethod.GET, produces = "application/zip")
public ResponseEntity<StreamingResponseBody> getFile() throws Exception {

    InputStream[] streamsToZip = getStreamsFromAzure();

    // You could cache already created zip files, maybe something like this:
    //   String[] pathsOfResourcesToZip = getPathsFromAzure();
    //   String zipId = getZipId(pathsOfResourcesToZip);
    //   if(isZipExist(zipId))
    //     // return that zip file
    //   else do the following

    StreamingResponseBody streamResponse = clientOut -> {
        FileOutputStream zipFileOut = new FileOutputStream("test.zip");

        ZipOutputStream zos = new ZipOutputStream(new SplitOutputStream(clientOut, zipFileOut));
        for (InputStream in : streamsToZip) {
            addToZipFile(zos, in);
        }
    };

    return ResponseEntity.ok()
            .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + "download.zip")
            .contentType(MediaType.parseMediaType("application/zip")).body(streamResponse);
}


private void addToZipFile(ZipOutputStream zos, InputStream fis) throws IOException {
    ZipEntry zipEntry = new ZipEntry(generateFileName());
    zos.putNextEntry(zipEntry);
    byte[] bytes = new byte[1024];
    int length;
    while ((length = fis.read(bytes)) >= 0) {
        zos.write(bytes, 0, length);
    }
    zos.closeEntry();
    fis.close();
}

public static class SplitOutputStream extends OutputStream {
    private final OutputStream out1;
    private final OutputStream out2;

    public SplitOutputStream(OutputStream out1, OutputStream out2) {
        this.out1 = out1;
        this.out2 = out2;
    }

    @Override public void write(int b) throws IOException {
        out1.write(b);
        out2.write(b);
    }

    @Override public void write(byte b[]) throws IOException {
        out1.write(b);
        out2.write(b);
    }

    @Override public void write(byte b[], int off, int len) throws IOException {
        out1.write(b, off, len);
        out2.write(b, off, len);
    }

    @Override public void flush() throws IOException {
        out1.flush();
        out2.flush();
    }

    /** Closes all the streams. If there was an IOException this throws the first one. */
    @Override public void close() throws IOException {
        IOException ioException = null;
        for (OutputStream o : new OutputStream[] {
                out1,
                out2 }) {
            try {
                o.close();
            } catch (IOException e) {
                if (ioException == null) {
                    ioException = e;
                }
            }
        }
        if (ioException != null) {
            throw ioException;
        }
    }
}

При первом запросе на архивирование набора ресурсов вы не будете знать, каким будет размер полученного zip-файла, поэтому вы не можете отправить длину вместе с ответом, так как вы передаете файл как онзаархивирован.

Но если вы ожидаете, что будут повторные запросы для того же набора ресурсов, которые будут заархивированы, то вы можете кэшировать ваши zip-файлы и просто возвращать их при любых последующих запросах;Вы также будете знать длину кэшированного zip-файла, чтобы вы могли отправлять его и в ответе.

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

...