Pergunta

Então, estou alimentando dados de arquivo para uma API que leva um Reader, e eu gostaria de uma maneira de relatar o progresso.

Parece que deveria ser simples escrever um FilterInputStream implementação que envolve o FileInputStream, acompanha o número de bytes lido versus o tamanho total do arquivo e dispara algum evento (ou chama alguns update() método) para relatar progresso fracionário.

(Como alternativa, poderia relatar bytes absolutos lidos, e outra pessoa poderia fazer as contas - talvez mais útil no caso de outras situações de streaming.)

Eu sei que já vi isso antes e posso até ter feito isso antes, mas não consigo encontrar o código e sou preguiçoso. Alguém conseguiu isso? Ou alguém pode sugerir uma abordagem melhor?


Um ano (e um pouco) depois ...

Implementei uma solução baseada na resposta de Adamski abaixo, e funcionou, mas depois de alguns meses de uso, eu não recomendaria. Quando você tem muitas atualizações, o disparo/manuseio eventos de progresso desnecessário se torna um custo enorme. O mecanismo básico de contagem é bom, mas muito melhor ter quem se preocupa com a pesquisa de progresso, em vez de empurrá -lo para eles.

(Se você souber o tamanho total, pode tentar disparar apenas um evento a cada alteração> 1% ou qualquer outra coisa, mas não vale a pena. E muitas vezes você não.)

Foi útil?

Solução

Aqui está uma implementação bastante básica que dispara PropertyChangeEvents Quando bytes adicionais são lidos. Algumas advertências:

  • A classe não suporta mark ou reset Operações, embora fossem fáceis de adicionar.
  • A classe não verifica se o número total de bytes lido excede o número máximo de bytes antecipados, embora isso sempre possa ser tratado pelo código do cliente ao exibir o progresso.
  • Eu não testei o código.

Código:

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

Outras dicas

Goiaba's com.google.common.io O pacote pode ajudá -lo um pouco. O seguinte é não compilado e não testado, mas deve colocá -lo no caminho certo.

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

Isso pode parecer muito código, mas acredito que é o mínimo que você vai se safar. (Observe outras respostas a esta pergunta, não dê completo Exemplos de código, por isso é difícil compará -los dessa maneira.)

A resposta de Adamski funciona, mas há um pequeno bug. O substituto read(byte[] b) Método chama o read(byte[] b, int off, int len) Método através da super classe.
Então updateProgress(long numBytesRead) é chamado duas vezes para cada ação de leitura e você acaba com um numBytesRead Isso é o dobro do tamanho do arquivo após a lida do arquivo total.

Não substituindo read(byte[] b) O método resolve o problema.

Se você está construindo um aplicativo de GUI, sempre há ProgressMonitorInputStream. Se não houver GUI envolvendo embrulhar um InputStream Na maneira como você descreve, é um acéfalo e leva menos tempo do que postar uma pergunta aqui.

Para concluir a resposta dada pelo @Kevin Bourillion, ela pode ser aplicada a um conteúdo de rede também usando essa técnica (que impede a leitura do fluxo duas vezes: um para tamanho e outro para conteúdo):

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

Onde o ProgressByteprocessor é uma classe interna:

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;
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top