InputStream ou Wrapper para relatórios de progresso
-
20-09-2019 - |
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.)
Solução
Aqui está uma implementação bastante básica que dispara PropertyChangeEvent
s Quando bytes adicionais são lidos. Algumas advertências:
- A classe não suporta
mark
oureset
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;
}
}