32

我有一个文件的 InputStream,我使用 apache poi 组件来读取它,如下所示:

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);

问题是我需要多次使用同一个流,并且 POIFSFileSystem 会在使用后关闭流。

缓存输入流中的数据然后将更多输入流提供给不同的 POIFSFileSystem 的最佳方法是什么?

编辑1:

缓存是指存储以备后用,而不是作为加速应用程序的一种方式。将输入流读入数组或字符串然后为每次使用创建输入流是否更好?

编辑2:

很抱歉重新提出问题,但在桌面和 Web 应用程序中工作时条件有所不同。首先,我从我的 tomcat Web 应用程序中的 org.apache.commons.fileupload.FileItem 获得的 InputStream 不支持标记,因此无法重置。

其次,我希望能够将文件保存在内存中,以便在处理文件时更快地访问和减少 io 问题。

4

10 回答 10

23

你可以用一个版本装饰 InputStream 被传递给POIFSFileSystem,当 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

于 2009-05-29T08:46:44.783 回答
23

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

bis在之前使用 inputStream 的任何地方使用。

于 2009-08-20T00:15:33.557 回答
5

这可以正常工作:

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();
 }
于 2011-02-15T15:20:32.680 回答
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();
        }
    }
}
于 2013-09-23T12:34:40.617 回答
1

如果文件不是那么大,则将其读入一个数组并从该数组中创建一个byte[]POI 。ByteArrayInputStream

如果文件很大,那么您不必在意,因为操作系统会尽其所能为您进行缓存。

[编辑] 使用Apache commons-io以有效的方式将文件读入字节数组。不要使用int read(),因为它逐字节读取文件非常慢!

如果你想自己做,使用一个File对象来获取长度,创建数组和从文件中读取字节的循环。您必须循环,因为read(byte[], int offset, int len)可以读取少于len字节(通常会)。

于 2009-05-29T08:43:25.023 回答
1

“缓存”到底是什么意思?您是否希望不同的 POIFSFileSystem 从流的开头开始?如果是这样,那么在您的 Java 代码中缓存任何内容绝对没有意义。它将由操作系统完成,只需打开一个新流。

或者你想在第一个 POIFSFileSystem 停止的地方继续阅读吗?那不是缓存,而且很难做到。如果你不能避免流被关闭,我能想到的唯一方法是编写一个瘦包装器来计算已读取的字节数,然后打开一个新流并跳过那么多字节。但是当 POIFSFileSystem 内部使用类似 BufferedInputStream 的东西时,这可能会失败。

于 2009-05-29T08:46:21.897 回答
1

这就是我将如何实现,以安全地与任何 InputStream 一起使用:

  • 编写自己的 InputStream 包装器,在其中创建一个临时文件以镜像原始流内容
  • 将从原始输入流中读取的所有内容转储到此临时文件中
  • 当流被完全读取时,您将在临时文件中镜像所有数据
  • 使用 InputStream.reset 将内部流切换(初始化)为 FileInputStream(mirrored_content_file)
  • 从现在开始你将失去原始流的引用(可以收集)
  • 添加一个新方法 release() ,它将删除临时文件并释放任何打开的流。
  • 您甚至可以从finalize调用 release()以确保临时文件已释放,以防您忘记调用 release() (大多数情况下您应该避免使用finalize,始终调用一个方法来释放对象资源)。请参阅为什么要实现 finalize()?
于 2009-05-29T14:12:29.397 回答
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 的一部分。

于 2010-12-18T01:08:30.350 回答
1

这个答案迭代了以前的答案1 | 2基于BufferInputStream. 主要变化是它允许无限重用。并负责关闭原始源输入流以释放系统资源。您的操作系统定义了这些限制,并且您不希望程序用完文件句柄(这也是为什么您应该始终“使用”响应,例如使用 apacheEntityUtils.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();
        }
    }
}

要重用它,如果不是,请先关闭它。

但一个限制是,如果在读取原始流的全部内容之前关闭流,则此装饰器将包含不完整的数据,因此请确保在关闭之前读取整个流。

于 2017-11-30T14:27:09.867 回答
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();
}
于 2013-06-17T11:25:15.363 回答