如何缓存输入流以供多次使用
-
06-09-2019 - |
题
我有一个文件的 InputStream,我使用 apache poi 组件来读取它,如下所示:
POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
问题是我需要多次使用同一个流,并且 POIFSFileSystem 在使用后关闭该流。
缓存输入流中的数据然后将更多输入流提供给不同的 POIFSFileSystem 的最佳方法是什么?
编辑1:
我所说的缓存是指存储供以后使用,而不是作为加速应用程序的一种方式。另外,将输入流读入数组或字符串,然后为每次使用创建输入流是否更好?
编辑2:
很抱歉重新提出这个问题,但是在桌面和 Web 应用程序中工作时,情况有些不同。首先,我从 Tomcat Web 应用程序中的 org.apache.commons.fileupload.FileItem 获得的 InputStream 不支持标记,因此无法重置。
其次,我希望能够将文件保留在内存中,以便在处理文件时加快访问速度并减少 io 问题。
解决方案
您可以装饰的InputStream传递给的 POIFSFileSystem 的一个版本是,当接近()被调用它具有复位响应():
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
可以在读一个字节[](啜食模式)的整个文件,然后将它传递给一个ByteArrayInputStream
其他提示
尝试的BufferedInputStream,这增加了标记和复位功能,以另一个输入流,并只覆盖其关闭方法:
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);
和使用bis
地方的inputStream物之前使用。
此正常工作:
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代码中任何东西;它将由OS进行,只是打开一个新的流。
或者你婉继续在第一POIFSFileSystem停止点读?这不是高速缓存,这是很难做到的。我能想到的,如果你不能避免被关闭流的唯一方法是写一个瘦包装计数多少字节被读取,然后打开一个新的数据流,并跳过许多字节。但是,当POIFSFileSystem内部使用有点像的BufferedInputStream可能会失败。
如果该文件不是那么大,读入一个byte[]
阵列和给POI从该阵列创建的ByteArrayInputStream
。
如果该文件是大的,那么你不应该关心,因为操作系统会为你是最好的,它可以做缓存。
[编辑]使用阿帕奇公地IO 以文件读入到在一个字节数组有效的方式。因为它通过字节读取文件字节不要使用int read()
是的非常的慢!
如果你想自己做,用File
对象来获取长度,创建数组和一个循环从文件中读取字节。必须因为read(byte[], int offset, int len)
环可以读取比len
字节少(且通常确实)。
这是我会怎样执行,以安全地使用的与任何输入流:
- 写你自己的输入流的包装在那里创建一个临时文件反射镜的原始流的内容
- 倾倒的一切读从原来的输入流入该临时文件
- 当流是完完全全读你会有的所有数据反映在临时文件
- 使用输入流.重置换(初始化)的内部流FileInputStream(mirrored_content_file)
- 从现在起,你将失去基准的原始流的(可收集)
- 添加一个新的方法释放()将拆除临时文件,并释放任何打开的流。
- 你甚至可以呼吁释放()从 最后确定 可以肯定的是临时文件是释放的情况下,你忘了呼吁释放()(大部分时间你应该避免使用 最后确定, 总是呼吁的一种方法释放物资源)。看看 为什么你会不会实现最终确定()?
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
. 。主要的变化是它允许无限重用。并负责关闭原始源输入流以释放系统资源。您的操作系统定义了这些限制,并且您不希望程序用完文件句柄(这也是为什么你应该总是“消耗”响应,例如与阿帕奇 EntityUtils.consumeQuietly()
). 编辑 更新了代码以处理使用的贪婪消费者 read(buffer, offset, length)
, ,在这种情况下可能会发生这样的情况 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();
}