如何防止DocumentBuilderFactory调用parse方法时自动关闭底层InputStream
问题根因
- 流内容已提前耗尽
你在调用builder.parse方法前,已经执行了batchFileUtils.fileToByteArray(inputStream),该方法会读取输入流的全部字节,将流的读取指针移动到末尾。即使后续流没有被关闭,再次读取也只能得到空内容,这是你第二次调用fileToByteArray得到空结果的核心原因,和parse是否关闭流没有直接关联。 - 自定义不可关闭流实现错误
你重写的close方法逻辑存在问题:当canBeClosed为true时,你调用的是super.close(),而父类InputStream的close是默认空实现,根本不会关闭你持有的底层input流。同时你只代理了read()的无参方法,没有覆盖read的重载方法、skip、available等方法,也可能引发未知问题。
解决方案
1. 修复自定义不可关闭输入流
正确的代理实现需要把所有InputStream的方法都代理到底层输入流,close方法仅在允许时关闭底层流:
import java.io.IOException; import java.io.InputStream; public class UncloseableInputStream extends InputStream { private final InputStream input; private boolean canBeClosed = false; public UncloseableInputStream(InputStream input) { this.input = input; } @Override public void close() throws IOException { if(canBeClosed) { // 关闭底层流,而非调用父类的空close方法 input.close(); } } @Override public int read() throws IOException { return input.read(); } @Override public int read(byte[] b) throws IOException { return input.read(b); } @Override public int read(byte[] b, int off, int len) throws IOException { return input.read(b, off, len); } @Override public long skip(long n) throws IOException { return input.skip(n); } @Override public int available() throws IOException { return input.available(); } @Override public synchronized void mark(int readlimit) { input.mark(readlimit); } @Override public synchronized void reset() throws IOException { input.reset(); } @Override public boolean markSupported() { return input.markSupported(); } public void allowToBeClosed() { canBeClosed = true; } }
使用修复后的包装类传入builder.parse,即可阻止parse方法关闭底层的原始输入流。
2. 解决流提前耗尽问题
如果需要多次读取输入流内容,推荐直接复用第一次读取到的字节数组,避免重复读取原始流:
builderFactory = DocumentBuilderFactory.newInstance(); builder = builderFactory.newDocumentBuilder(); byte[] bytesFile = null; try { bytesFile = batchFileUtils.fileToByteArray(inputStream); System.out.println("ok"); } catch (IOException e) { e.printStackTrace(); } // 用字节数组新建流传入parse,不会影响原始流 xmlDocument = builder.parse(new UncloseableInputStream(new ByteArrayInputStream(bytesFile))); // 后续需要使用流时,直接用字节数组重新构造即可 try { byte[] newBytes = batchFileUtils.fileToByteArray(new ByteArrayInputStream(bytesFile)); System.out.println("ok"); } catch (IOException e) { e.printStackTrace(); } // 所有操作完成后再关闭原始输入流 inputStream.close();
如果必须复用同一个原始输入流,可以先给原始流套上BufferedInputStream,它支持mark/reset操作,第一次读取前标记流的长度,读取完成后调用reset即可将指针复位到起始位置。
内容的提问来源于stack exchange,提问作者ulquiorra
相关产品推荐
相关产品推荐

