Java从ZipInputStream条目中创建InputStream

9

我希望编写一个方法,从单个InputStream中读取ZIP中的多个XML文件。

该方法将打开一个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();
    }
}

问题出在 "// READ THE STREAM" 这段代码。我有一个可行的解决方案,它创建了一个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打印条目内容,则可以工作:
Scanner sc = new Scanner(zis);
while (sc.hasNextLine())
{
    System.out.println(sc.nextLine());
}

但是...我目前使用的解析器(jdom2,但我也尝试过javax.xml.parsers.DocumentBuilderFactory)在解析数据后会关闭流:/。因此,我无法获取下一个条目并继续。

所以最后的问题是:

  • 有人知道一种DOM解析器不会关闭其流吗?
  • 还有其他方法可以从ZipEntry中获得InputStream吗?

谢谢。

4个回答

7

对Tim的解决方案进行一些小改进:在调用close()之前必须调用allowToBeClosed()的问题在于,当处理异常时,它使得正确关闭ZipInputStream变得棘手,并且会破坏Java 7中的try-with-resources语句。

我建议创建一个包装类,如下所示:

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

但是你的示例类没有提供任何关闭InputStream的方法。该流仍应该能够被关闭,可能需要通过单独的方法(例如创建forceClose()并委托给close())来实现。 - rtcarlson
你可以在UncloseableInputStream中添加一个forceClose()方法,但是没有必要,因为你可以直接调用zipIn.close()。而且使用zipIn.close()更好,因为就像上面的例子一样,它可以很好地与try-with-resources一起使用。 - Tony Abbott

4

感谢halfbit,我现在拥有了自己的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; }
}

3

您可以封装ZipInputStream并拦截对close()的调用。


它正在运行,非常感谢!如果有人感兴趣,请查看我的答案以获取更多细节。 - Tim Autin

0

如果您不介意使用外部依赖,Apache Commons IO 提供了一个便利类 CloseShieldInputStream 用于阻止 close() 调用。

private void readZip(InputStream is) throws IOException {

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

    while (entry != null) {

        if (entry.getName().endsWith(".xml")) {
            //commons-io 2.9 and later
            InputStream tempIs = CloseShieldInputStream.wrap(zis);
            //commons-io < 2.9
            //InputStream tempIs = new CloseShieldInputStream(zis);

            // READ THE STREAM

        }
        entry = zis.getNextEntry();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接