如何缓存输入流以供多次使用

发布于 2024-07-21 23:10:28 字数 564 浏览 6 评论 0原文

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

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);

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

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

编辑1:

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

编辑 2:

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

其次,我希望能够将文件保留在内存中,以便在处理文件时加快访问速度并减少 io 问题。

I have an InputStream of a file and i use apache poi components to read from it like this:

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);

The problem is that i need to use the same stream multiple times and the POIFSFileSystem closes the stream after use.

What is the best way to cache the data from the input stream and then serve more input streams to different POIFSFileSystem ?

EDIT 1:

By cache i meant store for later use, not as a way to speedup the application. Also is it better to just read up the input stream into an array or string and then create input streams for each use ?

EDIT 2:

Sorry to reopen the question, but the conditions are somewhat different when working inside desktop and web application.
First of all, the InputStream i get from the org.apache.commons.fileupload.FileItem in my tomcat web app doesn't support markings thus cannot reset.

Second, I'd like to be able to keep the file in memory for faster acces and less io problems when dealing with files.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

落叶缤纷 2024-07-28 23:10:28

这可以正常工作:

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();
 }

This works correctly:

byte[] bytes = getBytes(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes));

where getBytes is like this:

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();
 }
爱给你人给你 2024-07-28 23:10:28

您可以使用一个版本来装饰传递给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();
    }
}

testcase

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

EDIT 2

您可以以byte []读取整个文件(slurp模式)然后将其传递给 ByteArrayInputStream

you can decorate InputStream being passed to POIFSFileSystem with a version that when close() is called it respond with 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();
    }
}

testcase

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

EDIT 2

you can read the entire file in a byte[] (slurp mode) then passing it to a ByteArrayInputStream

⊕婉儿 2024-07-28 23:10:28

尝试 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

Try BufferedInputStream, which adds mark and reset functionality to another input stream, and just override its close method:

public class UnclosableBufferedInputStream extends BufferedInputStream {

    public UnclosableBufferedInputStream(InputStream in) {
        super(in);
        super.mark(Integer.MAX_VALUE);
    }

    @Override
    public void close() throws IOException {
        super.reset();
    }
}

So:

UnclosableBufferedInputStream  bis = new UnclosableBufferedInputStream (inputStream);

and use bis wherever inputStream was used before.

听闻余生 2024-07-28 23:10:28

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

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

What exactly do you mean with "cache"? Do you want the different POIFSFileSystem to start at the beginning of the stream? If so, there's absolutely no point caching anything in your Java code; it will be done by the OS, just open a new stream.

Or do you wan to continue reading at the point where the first POIFSFileSystem stopped? That's not caching, and it's very difficult to do. The only way I can think of if you can't avoid the stream getting closed would be to write a thin wrapper that counts how many bytes have been read and then open a new stream and skip that many bytes. But that could fail when POIFSFileSystem internally uses something like a BufferedInputStream.

骄傲 2024-07-28 23:10:28

使用下面的实现来进行更多自定义使用 -

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();
        }
    }
}

Use below implementation for more custom use -

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();
        }
    }
}
你怎么这么可爱啊 2024-07-28 23:10:28

如果文件不是那么大,请将其读入 byte[] 数组,并为 POI 提供从该数组创建的 ByteArrayInputStream

如果文件很大,那么您不必关心,因为操作系统会尽力为您进行缓存。

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

如果您想自己执行此操作,请使用 File 对象获取长度,创建数组和从文件读取字节的循环。 您必须循环,因为 read(byte[], int offset, int len) 可以读取少于 len 字节(通常如此)。

If the file is not that big, read it into a byte[] array and give POI a ByteArrayInputStream created from that array.

If the file is big, then you shouldn't care, since the OS will do the caching for you as best as it can.

[EDIT] Use Apache commons-io to read the File into a byte array in an efficient way. Do not use int read() since it reads the file byte by byte which is very slow!

If you want to do it yourself, use a File object to get the length, create the array and the a loop which reads bytes from the file. You must loop since read(byte[], int offset, int len) can read less than len bytes (and usually does).

心碎的声音 2024-07-28 23:10:28

这就是我的实现方式,可以安全地与任何 InputStream 一起使用:

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

This is how I would implemented, to be safely used with any InputStream :

  • write your own InputStream wrapper where you create a temporary file to mirror the original stream content
  • dump everything read from the original input stream into this temporary file
  • when the stream was completely read you will have all the data mirrored in the temporary file
  • use InputStream.reset to switch(initialize) the internal stream to a FileInputStream(mirrored_content_file)
  • from now on you will loose the reference of the original stream(can be collected)
  • add a new method release() which will remove the temporary file and release any open stream.
  • you can even call release() from finalize to be sure the temporary file is release in case you forget to call release()(most of the time you should avoid using finalize, always call a method to release object resources). see Why would you ever implement finalize()?
征棹 2024-07-28 23:10:28

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

要重用它,只需先将其关闭(如果不是)。

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

This answer iterates on previous ones 1|2 based on the BufferInputStream. The main changes are that it allows infinite reuse. And takes care of closing the original source input stream to free-up system resources. Your OS defines a limit on those and you don't want the program to run out of file handles (That's also why you should always 'consume' responses e.g. with the apache EntityUtils.consumeQuietly()). EDIT Updated the code to handle for gready consumers that use read(buffer, offset, length), in that case it may happen that BufferedInputStream tries hard to look at the source, this code protects against that use.

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();
        }
    }
}

To reuse it just close it first if it wasn't.

One limitation though is that if the stream is closed before the whole content of the original stream has been read, then this decorator will have incomplete data, so make sure the whole stream is read before closing.

囚你心 2024-07-28 23:10:28
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 的一部分。

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

This works. IOUtils is part of commons IO.

嘿看小鸭子会跑 2024-07-28 23:10:28

我只是在这里添加我的解决方案,因为这对我有用。 它基本上是前两个答案的组合:)

    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();
}

I just add my solution here, as this works for me. It basically is a combination of the top two answers :)

    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();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文