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

时间:2009-08-27 07:39:27

标签: java inputstream java-io

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

似乎应该直截了当地编写一个包含FilterInputStream的{​​{1}}实现,跟踪读取的字节数与总文件大小,并触发某些事件(或者,调用一些FileInputStream方法)来报告小数进度。

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

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


一年(稍后)......

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

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

5 个答案:

答案 0 :(得分:11)

这是一个相当基本的实现,在读取其他字节时触发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;
    }
}

答案 1 :(得分: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);
}

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

答案 2 :(得分:4)

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

不覆盖read(byte[] b)方法可以解决问题。

答案 3 :(得分:1)

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

答案 4 :(得分: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;
    }
}