如何缓存InputStream以供多种用途
我有一个文件的InputStream,我使用apache poi组件来读取它像这样:
POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
问题是我需要多次使用相同的流,POIFSFileSystem在使用后关闭流。
从输入流缓存数据然后将更多输入流提供给不同的POIFSFileSystem的最佳方法是什么?
编辑1:
通过缓存我的意思是存储供以后使用,而不是作为加速应用程序的方法。 也可以将输入流读入数组或字符串,然后为每次使用创建输入流?
编辑2:
很抱歉重新打开这个问题,但在桌面和Web应用程序中工作时条件有所不同。 首先,我从tomcat web app中的org.apache.commons.fileupload.FileItem获取的InputStream不支持标记,因此无法重置。
其次,我希望能够将文件保存在内存中,以便在处理文件时更快地访问和减少io问题。
你可以使用一个版本来装饰传递给POIFSFileSystem的 InputStream,当调用close()时它会以reset()响应:
class ResetOnCloseInputStream extends InputStream { private final InputStream decorated; public ResetOnCloseInputStream(InputStream anInputStream) { if (!anInputStream.markSupported()) { throw new IllegalArgumentException("marking not supported"); } anInputStream.mark( 1 << 24); // magic constant: BEWARE decorated = anInputStream; } @Override public void close() throws IOException { decorated.reset(); } @Override public int read() throws IOException { return decorated.read(); } }
测试用例
static void closeAfterInputStreamIsConsumed(InputStream is) throws IOException { int r; while ((r = is.read()) != -1) { System.out.println(r); } is.close(); System.out.println("========="); } public static void main(String[] args) throws IOException { InputStream is = new ByteArrayInputStream("sample".getBytes()); ResetOnCloseInputStream decoratedIs = new ResetOnCloseInputStream(is); closeAfterInputStreamIsConsumed(decoratedIs); closeAfterInputStreamIsConsumed(decoratedIs); closeAfterInputStreamIsConsumed(is); }
编辑2
你可以用byte [](slurp模式)读取整个文件,然后将它传递给ByteArrayInputStream
尝试BufferedInputStream,它将标记和重置function添加到另一个输入流,并覆盖其close方法:
public class UnclosableBufferedInputStream extends BufferedInputStream { public UnclosableBufferedInputStream(InputStream in) { super(in); super.mark(Integer.MAX_VALUE); } @Override public void close() throws IOException { super.reset(); } }
所以:
UnclosableBufferedInputStream bis = new UnclosableBufferedInputStream (inputStream);
并在之前使用inputStream的地方使用bis
。
这工作正常:
byte[] bytes = getBytes(inputStream); POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes));
其中getBytes是这样的:
private static byte[] getBytes(InputStream is) throws IOException { byte[] buffer = new byte[8192]; ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); int n; baos.reset(); while ((n = is.read(buffer, 0, buffer.length)) != -1) { baos.write(buffer, 0, n); } return baos.toByteArray(); }
使用以下实现进行更多自定义用途 –
public class ReusableBufferedInputStream extends BufferedInputStream { private int totalUse; private int used; public ReusableBufferedInputStream(InputStream in, Integer totalUse) { super(in); if (totalUse > 1) { super.mark(Integer.MAX_VALUE); this.totalUse = totalUse; this.used = 1; } else { this.totalUse = 1; this.used = 1; } } @Override public void close() throws IOException { if (used < totalUse) { super.reset(); ++used; } else { super.close(); } } }
你对“缓存”究竟是什么意思? 您是否希望在流的开头启动不同的POIFSFileSystem? 如果是这样,那么Java代码中的任何内容都没有任何缓存; 它将由操作系统完成,只需打开一个新流。
或者你想继续阅读第一个POIFSFileSystem停止的点吗? 这不是缓存,而且很难做到。 我能想到的唯一方法是,如果你无法避免流被关闭,那就是编写一个瘦包装器来计算已经读取了多少字节,然后打开一个新流并跳过那么多字节。 但是当POIFSFileSystem内部使用类似BufferedInputStream的东西时,这可能会失败。
如果文件不是那么大,请将其读入byte[]
数组,并为POI提供从该数组创建的ByteArrayInputStream
。
如果文件很大,那么你应该不在乎,因为操作系统会尽可能为你做缓存。
[编辑]使用Apache commons-io以有效的方式将文件读入字节数组。 不要使用int read()
因为它逐字节读取文件,这非常慢!
如果您想自己动手,请使用File
对象获取长度,创建数组以及从文件中读取字节的循环。 你必须循环,因为read(byte[], int offset, int len)
可以读取小于len
个字节(通常是)。
这是我实现的方式,可以安全地与任何InputStream一起使用:
- 编写自己的InputStream包装器,在其中创建临时文件以镜像原始流内容
- 将从原始输入流中读取的所有内容转储到此临时文件中
- 完全读取流后,您将在临时文件中镜像所有数据
- 使用InputStream.reset将内部流切换(初始化)为FileInputStream(mirrored_content_file)
- 从现在开始你将松开原始流的引用(可以收集)
- 添加一个新方法release(),它将删除临时文件并释放任何打开的流。
- 你甚至可以从finalize调用release()来确保临时文件是在你忘记调用release()时释放的(大多数时候你应该避免使用finalize ,总是调用一个方法来释放对象资源)。 看你为什么要实现finalize()?
public static void main(String[] args) throws IOException { BufferedInputStream inputStream = new BufferedInputStream(IOUtils.toInputStream("Foobar")); inputStream.mark(Integer.MAX_VALUE); System.out.println(IOUtils.toString(inputStream)); inputStream.reset(); System.out.println(IOUtils.toString(inputStream)); }
这很有效。 IOUtils是公共IO的一部分。
这个答案重复以前的1 | 2基于BufferInputStream
。 主要的变化是它允许无限重用。 并负责关闭原始源输入流以释放系统资源。 您的操作系统定义了这些限制,并且您不希望程序用完文件句柄( 这也是您应该总是’消费’响应的原因,例如使用apache EntityUtils.consumeQuietly()
)。 编辑更新了代码以处理使用read(buffer, offset, length)
gready使用者,在这种情况下, BufferedInputStream
可能会尝试查看源代码,此代码可以防止使用。
public class CachingInputStream extends BufferedInputStream { public CachingInputStream(InputStream source) { super(new PostCloseProtection(source)); super.mark(Integer.MAX_VALUE); } @Override public synchronized void close() throws IOException { if (!((PostCloseProtection) in).decoratedClosed) { in.close(); } super.reset(); } private static class PostCloseProtection extends InputStream { private volatile boolean decoratedClosed = false; private final InputStream source; public PostCloseProtection(InputStream source) { this.source = source; } @Override public int read() throws IOException { return decoratedClosed ? -1 : source.read(); } @Override public int read(byte[] b) throws IOException { return decoratedClosed ? -1 : source.read(b); } @Override public int read(byte[] b, int off, int len) throws IOException { return decoratedClosed ? -1 : source.read(b, off, len); } @Override public long skip(long n) throws IOException { return decoratedClosed ? 0 : source.skip(n); } @Override public int available() throws IOException { return source.available(); } @Override public void close() throws IOException { decoratedClosed = true; source.close(); } @Override public void mark(int readLimit) { source.mark(readLimit); } @Override public void reset() throws IOException { source.reset(); } @Override public boolean markSupported() { return source.markSupported(); } } }
要重复使用它,如果不是,请先关闭它。
但有一个限制是,如果在读取原始流的整个内容之前关闭流,则此装饰器将具有不完整的数据,因此请确保在关闭之前读取整个流。
我只是在这里添加我的解决方案,因为这对我有用。 它基本上是前两个答案的组合:)
private String convertStreamToString(InputStream is) { Writer w = new StringWriter(); char[] buf = new char[1024]; Reader r; is.mark(1 << 24); try { r = new BufferedReader(new InputStreamReader(is, "UTF-8")); int n; while ((n=r.read(buf)) != -1) { w.write(buf, 0, n); } is.reset(); } catch(UnsupportedEncodingException e) { Logger.debug(this.getClass(), "Cannot convert stream to string.", e); } catch(IOException e) { Logger.debug(this.getClass(), "Cannot convert stream to string.", e); } return w.toString(); }