InputStream или Reader для отчетов о проделанной работе - PullRequest
10 голосов
/ 27 августа 2009

Итак, я передаю данные файла в API, который принимает Reader, и я хотел бы сообщить о прогрессе.

Кажется, что было бы просто написать реализацию FilterInputStream, которая упаковывает FileInputStream, отслеживает количество прочитанных байтов по сравнению с общим размером файла и запускает некоторое событие (или вызывает некоторое * 1006). * метод), чтобы сообщить о частичном прогрессе.

(В качестве альтернативы он может сообщать об абсолютном количестве прочитанных байтов, а кто-то другой может сделать математику - может быть, в более общем случае, полезен в случае других потоковых ситуаций.)

Я знаю, что видел это раньше, и, возможно, даже делал это раньше, но я не могу найти код, и я ленивый. У кого-нибудь есть это? Или кто-то может предложить лучший подход?


Через год (и чуть-чуть) спустя ...

Я реализовал решение, основанное на ответе Адамски ниже, и оно сработало, но после нескольких месяцев использования я бы не стал его рекомендовать. Когда у вас много обновлений, запуск / обработка ненужных событий прогресса становится огромной ценой. Базовый механизм подсчета в порядке, но гораздо лучше иметь того, кто заботится о его проведении, вместо того, чтобы доводить его до сведения.

(Если вы знаете общий размер, вы можете попытаться запустить событие только после изменения> 1% или чего-то еще, но это не стоит того. И часто вы этого не делаете.)

Ответы [ 5 ]

11 голосов
/ 27 августа 2009

Вот довольно простая реализация, которая запускает PropertyChangeEvent s, когда считываются дополнительные байты. Некоторые предостережения:

  • Класс не поддерживает операции mark или reset, хотя их было бы легко добавить.
  • Класс не проверяет, превышает ли когда-либо общее число прочитанных байтов максимальное ожидаемое число байтов, хотя это всегда может быть решено кодом клиента при отображении прогресса.
  • Я не проверял код.

Код:

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;
    }
}
6 голосов
/ 26 мая 2010
Пакет

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

Это может выглядеть как много кода, но я верю, что это меньше всего, что вам сойдет с рук. (обратите внимание, что другие ответы на этот вопрос не дают полных примеров кода, поэтому сравнивать их сложно.)

4 голосов
/ 10 декабря 2010

Ответ Адамски работает, но есть небольшая ошибка. Переопределенный метод read(byte[] b) вызывает метод read(byte[] b, int off, int len) через суперкласс.
Таким образом, updateProgress(long numBytesRead) вызывается дважды для каждого действия чтения, и в итоге получается numBytesRead, который в два раза больше размера файла после прочтения всего файла.

Не переопределяющий read(byte[] b) метод решает проблему.

1 голос
/ 27 августа 2009

Если вы создаете приложение с графическим интерфейсом, всегда есть ProgressMonitorInputStream . Если нет никакого графического интерфейса пользователя, включающего завершение InputStream в том виде, в котором вы его описываете, не составляет труда и занимает меньше времени, чем размещение вопроса здесь.

0 голосов
/ 11 октября 2012

Чтобы завершить ответ, данный @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;
    }
}
...