用于进度报告的InputStream或Reader包装器

所以,我正在将文件数据提供给带有Reader的API,我想要一种报告进度的方法。

编写一个包装FileInputStreamFilterInputStream实现,跟踪读取的字节数与总文件大小,并触发一些事件(或调用一些update()方法)来报告小数进度似乎应该是直截了当的。 。

(或者,它可以报告绝对字节读取,而其他人可以进行数学计算 – 在其他流式传输情况下可能更常用。)

我知道我以前见过这个,我甚至可能以前做过,但我找不到代码,而且我很懒。 有没有人得到它? 或者有人可以提出更好的方法吗?


一年(有点)之后……

我在下面根据Adamski的答案实施了一个解决方案,并且它有效,但经过几个月的使用后我不推荐它。 当您有大量更新时,触发/处理不必要的进度事件会产生巨大的成本。 基本的计数机制很好,但是对于那些关心进展轮询的人来说要好得多,而不是把它推到他们身上。

(如果你知道总的大小,你可以尝试每隔> 1%的变化发射一次事件或其他什么,但这不值得麻烦。通常,你没有。)

这是一个相当基本的实现,在读取其他字节时触发PropertyChangeEvent 。 一些警告:

  • 该类不支持markreset操作,尽管这些操作很容易添加。
  • 该类不检查读取的总字节数是否超过预期的最大字节数,尽管在显示进度时总是可以由客户端代码处理。
  • 我没有测试代码。

码:

 public class ProgressInputStream extends FilterInputStream { private final PropertyChangeSupport propertyChangeSupport; private final long maxNumBytes; private volatile long totalNumBytesRead; public ProgressInputStream(InputStream in, long maxNumBytes) { super(in); this.propertyChangeSupport = new PropertyChangeSupport(this); this.maxNumBytes = maxNumBytes; } public long getMaxNumBytes() { return maxNumBytes; } public long getTotalNumBytesRead() { return totalNumBytesRead; } public void addPropertyChangeListener(PropertyChangeListener l) { propertyChangeSupport.addPropertyChangeListener(l); } public void removePropertyChangeListener(PropertyChangeListener l) { propertyChangeSupport.removePropertyChangeListener(l); } @Override public int read() throws IOException { int b = super.read(); updateProgress(1); return b; } @Override public int read(byte[] b) throws IOException { return (int)updateProgress(super.read(b)); } @Override public int read(byte[] b, int off, int len) throws IOException { return (int)updateProgress(super.read(b, off, len)); } @Override public long skip(long n) throws IOException { return updateProgress(super.skip(n)); } @Override public void mark(int readlimit) { throw new UnsupportedOperationException(); } @Override public void reset() throws IOException { throw new UnsupportedOperationException(); } @Override public boolean markSupported() { return false; } private long updateProgress(long numBytesRead) { if (numBytesRead > 0) { long oldTotalNumBytesRead = this.totalNumBytesRead; this.totalNumBytesRead += numBytesRead; propertyChangeSupport.firePropertyChange("totalNumBytesRead", oldTotalNumBytesRead, this.totalNumBytesRead); } return numBytesRead; } } 

Guava的com.google.common.io包可以帮到你一点点。 以下是未编译和未经测试但应该让您走上正确的轨道。

 long total = file1.length(); long progress = 0; final OutputStream out = new FileOutputStream(file2); boolean success = false; try { ByteStreams.readBytes(Files.newInputStreamSupplier(file1), new ByteProcessor() { public boolean processBytes(byte[] buffer, int offset, int length) throws IOException { out.write(buffer, offset, length); progress += length; updateProgressBar((double) progress / total); // or only update it periodically, if you prefer } public Void getResult() { return null; } }); success = true; } finally { Closeables.close(out, !success); } 

这可能看起来像很多代码,但我相信它是你逃脱的最少。 (注意这个问题的其他答案并没有提供完整的代码示例,因此很难以这种方式对它们进行比较。)

Adamski的答案有效,但有一个小错误。 重写的read(byte[] b)方法通过超类调用read(byte[] b, int off, int len)方法。
因此,对于每个读取操作, updateProgress(long numBytesRead)被调用两次,并且在读取完整个文件之后,最终得到的numBytesRead是文件大小的两倍。

不覆盖read(byte[] b)方法解决了这个问题。

如果您正在构建GUI应用程序,那么总是有ProgressMonitorInputStream 。 如果没有GUI涉及以您描述的方式包装InputStream是一个明智的选择,并且比在此处发布问题花费的时间更少。

为了完成@Kevin Bourillion给出的答案,它可以应用于网络内容以及使用这种技术(防止两次读取流:一个用于大小,一个用于内容):

  final HttpURLConnection httpURLConnection = (HttpURLConnection) new URL( url ).openConnection(); InputSupplier< InputStream > supplier = new InputSupplier< InputStream >() { public InputStream getInput() throws IOException { return httpURLConnection.getInputStream(); } }; long total = httpURLConnection.getContentLength(); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteStreams.readBytes( supplier, new ProgressByteProcessor( bos, total ) ); 

ProgressByteProcessor是一个内部类:

 public class ProgressByteProcessor implements ByteProcessor< Void > { private OutputStream bos; private long progress; private long total; public ProgressByteProcessor( OutputStream bos, long total ) { this.bos = bos; this.total = total; } public boolean processBytes( byte[] buffer, int offset, int length ) throws IOException { bos.write( buffer, offset, length ); progress += length - offset; publishProgress( (float) progress / total ); return true; } public Void getResult() { return null; } }