【问题标题】:How to Cache InputStream for Multiple Use如何缓存 InputStream 以供多次使用
【发布时间】:2010-10-29 19:50:57
【问题描述】:

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

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);

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

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

编辑 1:

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

编辑 2:

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

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

【问题讨论】:

  • 在我的帖子中查看我的“编辑 2”;我希望它有效

标签: java caching inputstream apache-poi


【解决方案1】:

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

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

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

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

【讨论】:

  • Read() 方法返回 int,我如何拆分字节:小端还是大端?
  • 读取总是返回 0-255 或 -1。首先检查 -1(流结束),然后您可以将其安全地转换为字节。
【解决方案2】:

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

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

【讨论】:

  • 假设输入流确实是可重置的并不是很明智。
【解决方案3】:

你可以装饰 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

【讨论】:

  • 在 anInputStream.mark( 1
  • 算了,你可以把它当成参数
  • 我只是放了 Integer.MAX_VALUE ,无论如何感谢它就像一个魅力。
  • @Michael-O mark 设置当reset 被调用docs.oracle.com/javase/7/docs/api/java/io/… 时要返回的流的主目录
  • this (.mark(Integer.MAX_VALUE)) 仅适用于
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 请检查问题的EDIT2:“...我得到的InputStream ...不支持标记因此无法重置...”
    • InputStream 是否支持并不重要。 BufferedInputStream 包裹另一个流,缓冲输入,并支持自己的标记。被覆盖的 close 方法也可以方便地重置它,只要它被使用。
    • 当您要求关闭它时,为什么要重置它?真的需要吗?我们不应该在需要的时候调用 reset() 吗?
    • @androiddeveloper 例如,如果您使用的库需要InputStream 并在使用后关闭它。
    • @Timmos 我不确定你是否理解我。我可以在他称为“reset()”的“close()”方法中看到。我不明白他为什么这样做。让 inputStreams 一直处于活动状态真的不是一件坏事吗?
    【解决方案6】:
    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 的一部分。

    【讨论】:

      【解决方案7】:

      这可以正常工作:

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

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

        • 它对你有用很好,但你不应该回答你的问题,而是回答所提出的问题;)
        • 这是我关于如何缓存 InputStream 以供多次使用的解决方案。这不是你提交的问题吗?
        • 感谢您的努力。对于我提交的问题,有时细节会有所不同。我问的问题更具体一点,Apache POI 使用的 Stream 的多次使用可能与 String一起使用,也可能不使用>。所以你实际上回答了更一般的问题,而不是我发布的更具体的问题。这就是为什么最具体的答案获胜。
        【解决方案9】:

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

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

        【讨论】:

          【解决方案10】:

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

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

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

          【讨论】:

            猜你喜欢
            • 2019-09-18
            • 2014-02-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-31
            相关资源
            最近更新 更多