#java
Вопрос:
У меня есть этот простой классный скрипт, который перечисляет записи в zip-файлах вместе с их размером несжатого файла
#!/usr/bin/env groovy
import groovy.transform.CompileStatic
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.nio.file.Path
import java.nio.file.Files
@CompileStatic
long zipSize(InputStream is) {
long totalSize = 0
byte[] buffer = new byte[0x10000]
new ZipInputStream(is).withCloseable { zis ->
while(true) {
ZipEntry zipEntry = zis.nextEntry
if(zipEntry == null) break
long entrySize = 0
while(true) {
int read = zis.read(buffer)
if(read <= 0) break
totalSize = read
entrySize = read
}
zis.closeEntry()
println("Uncompressed size of ${zipEntry.name} is ${entrySize} bytes")
}
}
return totalSize
}
args.each {
println("Opening '$it'")
Path path = Path.of(it)
long size = zipSize(Files.newInputStream(path))
println("Total uncompressed size of '$it' is $size bytes")
}
Я попытался запустить его с помощью zblg.zip который является zip-бомбой, загруженной отсюда, файл должен содержать несколько тысяч записей, каждая из которых имеет размер около 4 ГБ, однако вывод сценария просто:
Opening 'Downloads/zblg.zip'
Uncompressed size of 0 is 4294967240 bytes
Total uncompressed size of 'Downloads/zblg.zip' is 4294967240 bytes
это означает, что читается только первая запись, программа не показывает никаких ошибок и не создает никаких исключений, она просто действует так, как если бы zip-архив содержал только одну запись… Есть ли какие-либо ограничения, о ZipInputStream
которых я не знаю или которые я не могу найти в документации?
Редактировать:
Я также собрал пример Java 8, чтобы воспроизвести проблему
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.nio.file.Path;
import java.nio.file.Files;
public class zipSize {
private static long zipSize(InputStream is) throws IOException {
long totalSize = 0;
byte[] buffer = new byte[0x10000];
try(ZipInputStream zis = new ZipInputStream(is)) {
while(true) {
ZipEntry zipEntry = zis.getNextEntry();
if(zipEntry == null) break;
long entrySize = 0;
while(true) {
int read = zis.read(buffer);
if(read <= 0) break;
entrySize = read;
}
totalSize = entrySize;
zis.closeEntry();
System.out.printf("Uncompressed size of '%s' is %d bytesn", zipEntry.getName(), entrySize);
}
}
return totalSize;
}
public static void main(String[] args) throws IOException {
for(String arg : args) {
System.out.printf("Opening '%s'n", arg);
Path path = Paths.get(arg);
long size = zipSize(Files.newInputStream(path));
System.out.printf("Total uncompressed size of '%s' is %d bytesn", arg, size);
}
}
}
Комментарии:
1. Внутренний цикл while можно было бы на данный момент прокомментировать и просто использовать
zipEntry.getSize()
.2. хорошо, справедливо … но это не имеет никакого значения с точки зрения фактического результата
3. Вместо физического чтения блоков, просто переход к следующему почтовому индексу должен почти сработать, учитывая правильные сжатые размеры.
4. Я попробовал это и, к сожалению, не заметил никакой разницы
5. Я думаю, что в zip-файлах в конце есть своего рода таблица содержимого, так что вам не нужно сканировать весь файл, чтобы просто перечислить содержимое, поэтому размер конкретной записи не должен быть таким важным (но я не уверен на 100% в этом).
Ответ №1:
Zip — файл содержит своего рода таблицу содержимого со списком всех его записей под названием «центральный каталог» в конце архива.
Проблема в том , что java.util.zip.ZipInputStream
, будучи потоком, он не может прочитать его, пока его обязанности не будут уже выполнены.
Чтобы обойти эту проблему, он ищет локальный файл заголовка, часть метаданных начиная с магией 0x04034b50 число (источник здесь), который предваряет каждый элемент молнии, и он построен на предположении, что после каждой молнии ввода данных, там сразу новый локальный файл заголовка; если это не так, то ZipInputstream.getNextEntry()
возвращает пустую запись, которая, как он обычно сигналы о том, что все записи были прочитаны.
К сожалению, допускается наличие бессмысленных данных между одной записью и следующей или даже записей, которые больше не являются частью архива. Этот последний случай был на самом деле очень распространен в прошлом, когда zip-файлы записывались на несколько дискет, и физическое удаление записи в начале архива потребовало бы повторной вставки всех дисков по одному, чтобы переместить следующие записи и сжать архив. Поскольку это было явно непрактично, оптимизация заключалась в том, чтобы просто удалить запись из центрального каталога в конце файла, оставив заголовок локального файла и фактическое содержимое записи на месте.
В результате этого, java.util.zip.ZipInputStream
являются недостоверными, так как это может привести к ошибочным результатам (призрак записи и/или отсутствует архиве записях), однако ни один из этих вопросов будет, если вы можете с уверенностью предположить, что файл zip был создан для Java стандартная библиотека сама, используя для Java.утиль.на молнии.ZipOutputStream, а не оставлять пустые места между записями и записывает только реальные (что также представляется в Центральный каталог) записи.
По этой причине Zip-файлы, как правило, не подходят для потоковых приложений, поскольку единственный надежный способ их чтения-это сначала прочитать центральный каталог в конце файла (что java.util.zip.ZipFile
делает и, как таковое, не влияет на эту проблему).
Более подробная информация здесь
Комментарии:
1. однако пропуск записей (с чем я здесь столкнулся) не указан на этой странице между возможными симптомами