【问题标题】:InputStream or Reader wrapper for progress reporting用于进度报告的 InputStream 或 Reader 包装器
【发布时间】:2010-11-23 06:58:57
【问题描述】:

因此,我将文件数据提供给采用Reader 的 API,并且我想要一种报告进度的方法。

编写一个包装FileInputStreamFilterInputStream实现似乎应该很简单,跟踪读取的字节数与总文件大小,并触发一些事件(或者,调用一些@987654324 @method) 报告部分进度。

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

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


一年(和一点)之后……

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

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

【问题讨论】:

    标签: java inputstream java-io


    【解决方案1】:

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

    【讨论】:

    • 是的,我看过了。这是一个 GUI 应用程序,但它是一个相当复杂的应用程序,并且进度报告不仅仅是弹出标准 ProgressMonitor 的问题。包装InputStream 比发布问题花费的时间更少,但我永远不知道那里是否有人有更好的主意。
    【解决方案2】:

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

    • 该类不支持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;
        }
    }
    

    【讨论】:

    • 不错。我可能会忘记skip()。 :)
    • @David:说实话,实现 skip 意味着引入讨厌的 (int) 强制转换,所以如果你知道不需要它,我也会在这里抛出 UnsupportedOperationException。
    • 您还需要检查super.read() 是否返回负数(数据结束)。
    • 我认为那里仍然存在错误。 read(byte[]b) 的实现应该只是 return read(b, 0, b.length);没有进度更新)。在当前的实现中,调用read(byte[]b)会导致读取的字节被计数两次,因为FilterInputStream#read(byte[]b)的实现直接调用read(byte[]b,int off,int len)
    【解决方案3】:

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

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

    【讨论】:

    • 你知道用 url 而不是文件的任何可靠方法吗?让我烦恼的是查找 url 内容长度的方法。如此处所示,如果我们使用 gzip 压缩,似乎无法知道流的大小。 android-developers.blogspot.fr/2011/09/…
    • (那么这个评论不应该附在那个答案上吗?)
    【解决方案4】:

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

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

    【讨论】:

    • 这必须是@Adamski 答案的评论,至少如果您没有将更正的代码放入答案中,则它本身不是答案。
    【解决方案5】:

    要完成@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;
        }
    }
    

    【讨论】:

    • PS : 这不适用于 gzip 压缩的 urlconnections。
    猜你喜欢
    • 2015-05-30
    • 2011-01-02
    • 1970-01-01
    • 2013-12-20
    • 2011-05-18
    • 1970-01-01
    • 1970-01-01
    • 2015-08-28
    • 2020-07-04
    相关资源
    最近更新 更多