Java создает InputStream из записи ZipInputStream

Я хотел бы написать метод, который считывает несколько файлов XML внутри ZIP из одного InputStream.

Этот метод откроет ZipInputStream и для каждого файла xml получит соответствующий InputStream и передаст его моему синтаксическому анализатору XML. Вот скелет метода:

private void readZip(InputStream is) throws IOException {

    ZipInputStream zis = new ZipInputStream(is);
    ZipEntry entry = zis.getNextEntry();

    while (entry != null) {

        if (entry.getName().endsWith(".xml")) {

            // READ THE STREAM
        }
        entry = zis.getNextEntry();
    }
}

Проблемной частью является «// ПРОЧИТАЙТЕ ПОТОК». У меня есть рабочее решение, которое состоит в том, чтобы создать ByteArrayInputStream и передать его моему парсеру. Но он использует буфер, и для больших файлов я получаю OutOfMemoryError. Вот код, если кому-то еще интересно:

int count;
byte buffer[] = new byte[2048];
ByteArrayOutputStream out = new ByteArrayOutputStream();
while ((count = zis.read(buffer)) != -1) { out.write(buffer, 0, count); }       
InputStream is = new ByteArrayInputStream(out.toByteArray());

Идеальным решением было бы скормить парсеру оригинальный ZipInputStream. Это должно работать, потому что это работает, если я просто распечатаю содержимое записи с помощью сканера:

Scanner sc = new Scanner(zis);
while (sc.hasNextLine())
{
    System.out.println(sc.nextLine());
}

Но... Парсер, который я сейчас использую (jdom2, но я также пробовал с javax.xml.parsers.DocumentBuilderFactory), закрывает поток после разбора данных:/. Поэтому я не могу получить следующую запись и продолжить.

Итак, наконец, вопрос:

  • Кто-нибудь знает парсер DOM, который не закрывает свой поток?
  • Есть ли другой способ получить InputStream из ZipEntry?

Спасибо.


person Tim Autin    schedule 16.11.2013    source источник


Ответы (3)


Вы можете обернуть ZipInputStream и перехватить вызов close().

person halfbit    schedule 16.11.2013
comment
Работает, большое спасибо! Если кому-то интересно, смотрите мой ответ для более подробной информации. - person Tim Autin; 16.11.2013

Небольшое улучшение решения Тима: проблема с вызовом allowToBeClosed() перед close() заключается в том, что это делает закрытие ZipInputStream должным образом при обработке исключений сложным и нарушит оператор try-with-resources в Java 7.

Я предлагаю создать класс-оболочку следующим образом:

public class UncloseableInputStream extends InputStream {
  private final InputStream input;

  public UncloseableInputStream(InputStream input) {
    this.input = input;
  }

  @Override
  public void close() throws IOException {} // do not close the wrapped stream

  @Override
  public int read() throws IOException {
    return input.read();
  }

  // delegate all other InputStream methods as with read above
}

который затем можно безопасно использовать следующим образом:

try (ZipInputStream zipIn = new ZipInputStream(...))
{
  DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  ZipEntry entry;
  while (null != (entry = zipIn.getNextEntry()))
  {
    if ("file.xml".equals(entry.getName())
    {
      Document doc = db.parse(new UncloseableInputStream(zipIn));
    }
  }
}
person Tony Abbott    schedule 11.12.2013
comment
Но ваш класс-пример не дает возможности когда-либо закрыть InputStream. Поток по-прежнему должен иметь возможность быть закрытым, возможно, с помощью отдельного метода (например, create forceClose(), который делегирует close()). - person rtcarlson; 31.07.2014
comment
Вы можете добавить метод forceClose() в UncloseableInputStream, но в этом нет необходимости, поскольку вы можете просто вызвать zipIn.close(). И использование zipIn.close() лучше, потому что, как и в приведенном выше примере, оно хорошо сочетается с попыткой с ресурсами. - person Tony Abbott; 09.09.2014

Благодаря халфбиту я получил собственный класс ZipInputStream, который переопределяет метод close:

import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipInputStream;

public class CustomZipInputStream extends ZipInputStream {

    private boolean _canBeClosed = false;

    public CustomZipInputStream(InputStream is) {
        super(is);
    }

    @Override
    public void close() throws IOException {

        if(_canBeClosed) super.close();
    }

    public void allowToBeClosed() { _canBeClosed = true; }
}
person Tim Autin    schedule 16.11.2013