我有一个文件的InputStream,我使用apache poi组件来读取它:
POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
问题是我需要多次使用相同的流,POIFSFileSystem在使用后关闭流。
从输入流缓存数据然后将更多输入流提供给不同的POIFSFileSystem的最佳方法是什么?
编辑1:
通过缓存我的意思是存储供以后使用,而不是加速应用程序的方法。最好是将输入流读入数组或字符串,然后为每次使用创建输入流?
编辑2:
很抱歉重新打开这个问题,但在桌面和Web应用程序中工作时条件有所不同。 首先,我从tomcat web app中的org.apache.commons.fileupload.FileItem获取的InputStream不支持标记,因此无法重置。
其次,我希望能够将文件保存在内存中,以便在处理文件时更快地访问和减少io问题。
答案 0 :(得分:20)
尝试BufferedInputStream,它将标记和重置功能添加到另一个输入流,并覆盖其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
。
答案 1 :(得分:17)
你可以使用一个版本来装饰传递给 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);
}
你可以用byte [](slurp模式)读取整个文件,然后将它传递给ByteArrayInputStream
答案 2 :(得分:4)
这是正常的:
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();
}
答案 3 :(得分:2)
使用以下实现进行更多自定义用途 -
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();
}
}
}
答案 4 :(得分:1)
如果文件不是那么大,请将其读入byte[]
数组,并为POI提供从该数组创建的ByteArrayInputStream
。
如果文件很大,那么你应该不在乎,因为操作系统会尽可能为你做缓存。
[编辑]使用Apache commons-io以有效的方式将文件读入字节数组。不要使用int read()
,因为它逐字节读取文件,非常慢!
如果您想自己动手,请使用File
对象获取长度,创建数组以及从文件中读取字节的循环。您必须循环,因为read(byte[], int offset, int len)
可以读取少于len
个字节(并且通常会这样做)。
答案 5 :(得分:1)
你对“缓存”究竟是什么意思?您是否希望不同的POIFSFileSystem从流的开头开始?如果是这样,那么Java代码中的任何内容都没有任何缓存;它将由操作系统完成,只需打开一个新流。
或者您想继续阅读第一个POIFSFileSystem停止的位置吗?这不是缓存,而且很难做到。我能想到的唯一方法是,如果你无法避免流被关闭,那就是编写一个瘦包装器来计算已经读取了多少字节,然后打开一个新流并跳过那么多字节。但是当POIFSFileSystem内部使用类似BufferedInputStream的东西时,这可能会失败。
答案 6 :(得分:1)
这是我实现的方式,可以安全地与任何InputStream一起使用:
答案 7 :(得分:1)
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的一部分。
答案 8 :(得分:1)
此答案基于BufferInputStream
对以前的 1 | 2 进行迭代。主要的变化是它允许无限重用。并负责关闭原始源输入流以释放系统资源。您的操作系统定义了这些限制,并且您不希望程序用完文件句柄(这也是您应该总是'消费'响应的原因,例如使用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();
}
}
}
要重复使用它,如果不是,请先关闭它。
但有一个限制是,如果在读取原始流的整个内容之前关闭流,则此装饰器将具有不完整的数据,因此请确保在关闭之前读取整个流。
答案 9 :(得分:0)
我只是在这里添加我的解决方案,因为这对我有用。它基本上是前两个答案的组合:)
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();
}