12

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

FilterInputStream编写一个包装 . 的实现似乎应该很简单FileInputStream,跟踪读取的字节数与总文件大小,并触发某些事件(或调用某些update()方法)以报告分数进度。

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

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


一年(还有一点)之后……

我根据下面 Adamski 的回答实施了一个解决方案,它有效,但经过几个月的使用,我不推荐它。当您有大量更新时,触发/处理不必要的进度事件会成为巨大的成本。基本的计数机制很好,但让关心进度的人为它投票要好得多,而不是把它推给他们。

(如果您知道总大小,则可以尝试仅在每 > 1% 的更改或其他情况下触发一个事件,但这并不值得麻烦。而且通常情况下,您不这样做。)

4

5 回答 5

14

PropertyChangeEvent这是一个相当基本的实现,当读取额外的字节时会触发s 。一些警告:

  • 该类不支持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;
    }
}
于 2009-08-27T08:14:43.183 回答
6

Guavacom.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<Void>() {
        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);
}

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

于 2010-05-25T20:42:31.297 回答
5

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

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

于 2010-12-10T14:13:31.610 回答
1

如果您正在构建一个 GUI 应用程序,总会有ProgressMonitorInputStream。如果不涉及 GUIInputStream以您描述的方式包装 an 是不费吹灰之力的,并且比在此处发布问题所需的时间更少。

于 2009-08-27T07:49:31.563 回答
0

要完成@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;
    }
}
于 2012-10-11T13:58:04.353 回答