我正在尝试从网络下载 mp4 文件。我想异步执行并跟踪进度,以便可以将其显示在进度栏中。
我的代码如下所示:
URLConnection con = url.openConnection();
ReadableByteChannel rbc = Channels.newChannel(con.getInputStream());
FileOutputStream fos = new FileOutputStream(file);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
使用transferFrom创建一个循环,每次仅读取32KB并递增位置是一个很好的做法,还是有更好的方法可以让我跟踪下载进度?我怎么知道何时停止转移?
我刚刚发现你可以通过 HTTP header 字段获取文件大小:
con.getHeaderFields().get("Content-Length").get(0)
现在知道文件大小应该使我能够实现前面提到的循环。
InputStream in = con.getInputStream();
long fileSize = Long.parseLong(con.getHeaderFields().get("Content-Length").get(0));
FileOutputStream fos = new FileOutputStream(file);
for (long offset = 0; offset < fileSize; offset += transferBytesAtOnce) {
in.skip(offset);
ReadableByteChannel rbc = Channels.newChannel(in);
fos.getChannel().transferFrom(rbc, offset, transferBytesAtOnce);
System.out.println(offset + "/" + fileSize);
}
但是,正如我所料,这个解决方案的性能非常糟糕。当将transferBytesAtOnce变量保持在低水平时,它的下载速度非常慢。当使用高值时,它会取消下载。
编辑:Nvm,将跳过替换为
if (offset > 0) {
in.skip(transferBytesAtOnce);
}
它现在工作得更好了,但仍然不如不跟踪进度的解决方案那么快。
最佳答案
灵感来自this我写这个类
public class ReadableConsumerByteChannel implements ReadableByteChannel {
private final ReadableByteChannel rbc;
private final IntConsumer onRead;
private int totalByteRead;
public ReadableConsumerByteChannel(ReadableByteChannel rbc, IntConsumer onBytesRead) {
this.rbc = rbc;
this.onRead = onBytesRead;
}
@Override
public int read(ByteBuffer dst) throws IOException {
int nRead = rbc.read(dst);
notifyBytesRead(nRead);
return nRead;
}
protected void notifyBytesRead(int nRead){
if(nRead<=0) {
return;
}
totalByteRead += nRead;
onRead.accept(totalByteRead);
}
@Override
public boolean isOpen() {
return rbc.isOpen();
}
@Override
public void close() throws IOException {
rbc.close();
}
}
然后像这样包裹旧的来使用它
URLConnection con = new URL(url).openConnection();
int fileSize = con.getContentLength();
ReadableByteChannel rbc = Channels.newChannel(con.getInputStream());
ReadableConsumerByteChannel rcbc = new ReadableConsumerByteChannel(rbc,(b)->{
System.out.println("Read "+b +"/"+fileSize);
});
FileOutputStream fos = new FileOutputStream(file);
fos.getChannel().transferFrom(rcbc, 0, Long.MAX_VALUE);
关于Java NIO FileChannels 跟踪进度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30405695/