代码之家  ›  专栏  ›  技术社区  ›  Azder

如何缓存多用途的输入流

  •  23
  • Azder  · 技术社区  · 15 年前

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

    POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
    

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

    从输入流缓存数据,然后向不同的poifsffilesystem提供更多输入流的最佳方法是什么?

    编辑1:

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

    编辑2:

    很抱歉重新打开此问题,但在桌面和Web应用程序中工作时,情况有所不同。 首先,我从Tomcat Web应用程序中的org.apache.commons.fileupload.fileitem获得的输入流不支持标记,因此无法重置。

    第二,我希望能够将文件保存在内存中,以便在处理文件时更快地访问并减少IO问题。

    10 回复  |  直到 7 年前
        1
  •  17
  •   dfa    15 年前

    您可以修饰正在传递给的输入流 POIFS文件系统 当调用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

    您可以在字节[]中读取整个文件(slurp模式),然后将其传递给bytearrayinputstream

        2
  •  20
  •   Tomasz    15 年前

    尝试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的地方。

        3
  •  4
  •   Kaba Aboubacar    13 年前

    这项工作正常:

    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();
     }
    
        4
  •  2
  •   Thiago Silveira    10 年前

    使用下面的实现以获得更多的自定义使用-

    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();
            }
        }
    }
    
        5
  •  1
  •   Michael Borgwardt    15 年前

    “缓存”究竟是什么意思?是否希望不同的poifsffilesystem从流的开头开始?如果是这样,在Java代码中绝对没有缓存任何东西;它将由OS完成,只需打开一个新的流即可。

    或者您想在第一个poifsffilesystem停止时继续读取?这不是缓存,很难做到。如果无法避免流被关闭,我唯一能想到的方法就是编写一个计算已读取多少字节的瘦包装器,然后打开一个新的流并跳过这些字节。但是当poifsfilesystem内部使用bufferedInputstream之类的东西时,这可能会失败。

        6
  •  1
  •   Aaron Digulla    15 年前

    如果文件不太大,请将其读取到 byte[] 排列并给出POI A ByteArrayInputStream 从该数组创建。

    如果文件很大,那么您不应该在意,因为操作系统会尽可能为您做缓存。

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

    如果你想自己做,用 File 对象获取长度,创建数组和从文件中读取字节的A循环。你必须循环自 read(byte[], int offset, int len) 可读取小于 len 字节(通常是这样)。

        7
  •  1
  •   Community Neeleshkumar S    7 年前

    这是我将如何实现的,以便安全地与任何输入流一起使用:

    • 编写自己的inputstream包装器,在其中创建临时文件以镜像原始流内容
    • 将从原始输入流读取的所有内容转储到此临时文件中
    • 当流被完全读取时,您将在临时文件中镜像所有数据。
    • 使用inputstream.reset将内部流切换(初始化)到file inputstream(镜像的_content_文件)
    • 从现在起,您将释放原始流的引用(可以收集)
    • 添加一个新的方法release(),它将删除临时文件并释放任何打开的流。
    • 甚至可以从调用release()。 定稿 为了确保临时文件是释放的,以防忘记调用release()(大多数情况下应避免使用 定稿 ,始终调用方法以释放对象资源)。看见 Why would you ever implement finalize()?
        8
  •  1
  •   Daniel Kaplan    14 年前
    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是commons io的一部分。

        9
  •  1
  •   bric3    7 年前

    这个答案重复前面的答案 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();
            }
        }
    }
    

    要重用它,如果没有,就先关闭它。

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

        10
  •  0
  •   FuePi    11 年前

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

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