Чтение из ZipInputStream в ByteArrayOutputStream

Я пытаюсь прочитать один файл из java.util.zip.ZipInputStream и скопировать его в java.io.ByteArrayOutputStream (чтобы затем я мог создать java.io.ByteArrayInputStream и передать его сторонней библиотеке, которая в конечном итоге закроет поток, и я не хочу, чтобы мой ZipInputStream закрывается).

Я, вероятно, упускаю здесь что-то основное, но я никогда не ввожу здесь цикл while:

ByteArrayOutputStream streamBuilder = new ByteArrayOutputStream();
int bytesRead;
byte[] tempBuffer = new byte[8192*2];
try {
    while ((bytesRead = zipStream.read(tempBuffer)) != -1) {
        streamBuilder.write(tempBuffer, 0, bytesRead);
    }
} catch (IOException e) {
    // ...
}

Что мне не хватает, что позволит мне скопировать поток?

Изменить:

Я должен был упомянуть ранее, что этот ZipInputStream не исходит из файла, поэтому я не думаю, что смогу использовать ZipFile. Это происходит из файла, загруженного через сервлет.

Кроме того, я уже вызывал getNextEntry() на ZipInputStream, прежде чем добраться до этого фрагмента кода. Если я не попытаюсь скопировать файл в другой InputStream (через OutputStream, упомянутый выше), а просто передам ZipInputStream в свою стороннюю библиотеку, библиотека закроет поток, и я больше ничего не смогу сделать, например, справиться с оставшиеся файлы в потоке.


person pkaeding    schedule 15.09.2008    source источник
comment
Так что же возвращает zipEntry.getSize()?   -  person Boris Bokowski    schedule 16.09.2008
comment
zipEntry.getSize() возвращает разумное число, в данном случае 28689.   -  person pkaeding    schedule 16.09.2008
comment
Возможно, сейчас вам все равно, но вы можете избежать копирования всех данных и избежать закрытия потока сторонней библиотекой, если вы оберните исходный входной поток (zipStream) и переопределите метод закрытия. 1) Сделать публичный класс DontCloseInputStream расширяющим FilterInputStream. 2) Создайте конструктор (InputStream in), который вызывает super(in) 3) Переопределите метод close и ничего не делайте 4) Создайте новый DontCloseInputStream(zipStream) 5) передайте его в библиотеку. И вуа ла   -  person helios    schedule 31.12.2009
comment
А для копирования InputStream в OutputStream есть служебный класс Streams в библиотеке commons-fileupload (Apache). Вы выполняете Streams.copy(in, out, close?), и все готово.   -  person helios    schedule 31.12.2009


Ответы (10)


Ваш цикл выглядит корректным — что возвращает следующий код (только сам по себе)?

zipStream.read(tempBuffer)

если он возвращает -1, то zipStream закрывается до того, как вы его получите, и все ставки сняты. Пришло время использовать ваш отладчик и убедиться, что то, что вам передается, действительно допустимо.

Когда вы вызываете getNextEntry(), возвращает ли она значение и являются ли данные в записи значимыми (т. е. возвращает ли getCompressedSize() допустимое значение)? ЕСЛИ вы просто читаете Zip-файл, в который не встроены zip-записи для упреждающего чтения, тогда ZipInputStream вам не подойдет.

Несколько полезных фактов о формате Zip:

Каждый файл, встроенный в zip-файл, имеет заголовок. Этот заголовок может содержать полезную информацию (например, сжатую длину потока, его смещение в файле, CRC) — или он может содержать некоторые магические значения, которые в основном говорят: «Информации нет в заголовке потока, вы должны проверить Zip пост-амбула'.

Затем каждый zip-файл имеет таблицу, прикрепленную к концу файла, которая содержит все записи zip вместе с реальными данными. Таблица в конце обязательна, и значения в ней должны быть правильными. Напротив, значения, встроенные в поток, не должны предоставляться.

Если вы используете ZipFile, он читает таблицу в конце zip. Если вы используете ZipInputStream, я подозреваю, что getNextEntry() пытается использовать записи, встроенные в поток. Если эти значения не указаны, то ZipInputStream не знает, какой длины может быть поток. Алгоритм inflate является самозавершающимся (на самом деле вам не нужно знать несжатую длину выходного потока, чтобы полностью восстановить вывод), но возможно, что Java-версия этого ридера не очень хорошо справляется с этой ситуацией.

Я скажу, что довольно необычно иметь сервлет, возвращающий ZipInputStream (гораздо чаще получать inflatorInputStream, если вы собираетесь получать сжатый контент.

person Kevin Day    schedule 16.09.2008
comment
ZipInputStream в java НЕ ОБРАЩАЕТСЯ С ЭТИМ ХОРОШО. Спасибо за публикацию. - person Ethan Heilman; 17.07.2009

Вероятно, вы пробовали читать из FileInputStream вот так:

ZipInputStream in = new ZipInputStream(new FileInputStream(...));

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

Вы можете использовать java.util.zip.ZipFile и такую ​​библиотеку, как IOUtils из Apache Commons IO или ByteStreams из Guava, которые помогут вам скопировать поток.

Пример:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try (ZipFile zipFile = new ZipFile("foo.zip")) {
    ZipEntry zipEntry = zipFile.getEntry("fileInTheZip.txt");

    try (InputStream in = zipFile.getInputStream(zipEntry)) {
        IOUtils.copy(in, out);
    }
}
person Benedikt Waldvogel    schedule 15.09.2008

Я бы использовал IOUtils из проект Commons.io.

IOUtils.copy(zipStream, byteArrayOutputStream);
person ScArcher2    schedule 15.09.2008
comment
Похоже, это может сработать. Попробую завтра, когда буду на работе. Спасибо. - person pkaeding; 16.09.2008

Вы пропустили звонок

Запись ZipEntry = (ZipEntry) zipStream.getNextEntry();

для позиционирования первого байта, распакованного из первой записи.

 ByteArrayOutputStream streamBuilder = new ByteArrayOutputStream();
 int bytesRead;
 byte[] tempBuffer = new byte[8192*2];
 ZipEntry entry = (ZipEntry) zipStream.getNextEntry();
 try {
     while ( (bytesRead = zipStream.read(tempBuffer)) != -1 ){
        streamBuilder.write(tempBuffer, 0, bytesRead);
     }
 } catch (IOException e) {
      ...
 }
person Juan Ignacio    schedule 03.04.2012

Вы можете реализовать свою собственную оболочку вокруг ZipInputStream, которая игнорирует функцию close(), и передать ее сторонней библиотеке.

thirdPartyLib.handleZipData(new CloseIgnoringInputStream(zipStream));


class CloseIgnoringInputStream extends InputStream
{
    private ZipInputStream stream;

    public CloseIgnoringInputStream(ZipInputStream inStream)
    {
        stream = inStream;
    }

    public int read() throws IOException {
        return stream.read();
    }

    public void close()
    {
        //ignore
    }

    public void reallyClose() throws IOException
    {
        stream.close();
    }
}
person jt.    schedule 16.09.2008

Я бы вызывал getNextEntry() в ZipInputStream, пока он не окажется в нужной записи (используйте ZipEntry.getName() и т. д.). Вызов getNextEntry() переместит «курсор» к началу возвращаемой записи. Затем используйте ZipEntry.getSize(), чтобы определить, сколько байтов вы должны прочитать, используя zipInputStream.read().

person Boris Bokowski    schedule 16.09.2008
comment
На самом деле я вызвал getNextEntry(), прежде чем добраться до этого фрагмента. Я просто добавил некоторые пояснения к вопросу. - person pkaeding; 16.09.2008

Непонятно, как вы получили zipStream. Это должно работать, когда вы получаете это так:

  zipStream = zipFile.getInputStream(zipEntry)
person Boris Bokowski    schedule 15.09.2008
comment
Я только что добавил разъяснение по этому поводу, но оно не из файла. - person pkaeding; 16.09.2008

Непонятно, как вы получили zipStream. Это должно работать, когда вы получаете это так:

  zipStream = zipFile.getInputStream(zipEntry)

Если вы получаете ZipInputStream из ZipFile, вы можете получить один поток для сторонней библиотеки, позволить ей использовать его, и вы получите другой входной поток, используя код ранее.

Помните, что поток ввода — это курсор. Если у вас есть все данные (например, ZipFile), вы можете запросить N курсоров над ним.

Другой случай - если у вас есть только входной поток "GZip", только заархивированный поток байтов. В этом случае ваш буфер ByteArrayOutputStream имеет смысл.

person helios    schedule 15.09.2008

Пожалуйста, попробуйте код ниже

private static byte[] getZipArchiveContent(File zipName) throws WorkflowServiceBusinessException {

  BufferedInputStream buffer = null;
  FileInputStream fileStream = null;
  ByteArrayOutputStream byteOut = null;
  byte data[] = new byte[BUFFER];

  try {
   try {
    fileStream = new FileInputStream(zipName);
    buffer = new BufferedInputStream(fileStream);
    byteOut = new ByteArrayOutputStream();

    int count;
    while((count = buffer.read(data, 0, BUFFER)) != -1) {
     byteOut.write(data, 0, count);
    }
   } catch(Exception e) {
    throw new WorkflowServiceBusinessException(e.getMessage(), e);
   } finally {
    if(null != fileStream) {
     fileStream.close();
    }
    if(null != buffer) {
     buffer.close();
    }
    if(null != byteOut) {
     byteOut.close();
    }
   }
  } catch(Exception e) {
   throw new WorkflowServiceBusinessException(e.getMessage(), e);
  }
  return byteOut.toByteArray();

 }
person Dmytro    schedule 19.01.2010

Проверьте, находится ли входной поток в начале.

В остальном, как реализация: я не думаю, что вам нужно писать в поток результатов, пока вы читаете, если только вы не обрабатываете именно этот поток в другом потоке.

Просто создайте массив байтов, прочитайте входной поток, затем создайте выходной поток.

person Sunny Milenov    schedule 15.09.2008