Чтение из ZipInputStream в ByteArrayOutputStream - PullRequest
17 голосов
/ 16 сентября 2008

Я пытаюсь прочитать один файл из 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 моей сторонней библиотеке, библиотека закрывает поток, и я ничего не могу сделать больше, например, работа с оставшимися файлами в потоке.

Ответы [ 10 ]

7 голосов
/ 16 сентября 2008

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

zipStream.read(tempBuffer)

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

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

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

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

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

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

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

7 голосов
/ 16 сентября 2008

Вы, вероятно, пытались читать с 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);
    }
}
4 голосов
/ 03 апреля 2012

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

ZipEntry entry = (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) {
      ...
 }
4 голосов
/ 16 сентября 2008

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

IOUtils.copy(zipStream, byteArrayOutputStream);
3 голосов
/ 16 сентября 2008

Вы можете реализовать свою собственную оболочку для 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();
    }
}
1 голос
/ 16 сентября 2008

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

0 голосов
/ 19 января 2010

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

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();

 }
0 голосов
/ 16 сентября 2008

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

  zipStream = zipFile.getInputStream(zipEntry)

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

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

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

0 голосов
/ 16 сентября 2008

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

  zipStream = zipFile.getInputStream(zipEntry)
0 голосов
/ 16 сентября 2008

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...