基于Java Commons IO的文件下载器速度受限至1MB/s+求助
网络文件下载器速度受限问题排查
近期我用Java结合Commons IO开发了一款网络文件下载器,但遇到了瓶颈:下载速度始终无法突破1MB/s,而同一文件用浏览器下载能稳定达到3MB/s。以下是相关代码,求解决思路:
Downloader类代码
package com.application.steammachine; import com.github.junrar.Archive; import com.github.junrar.Junrar; import com.github.junrar.exception.RarException; import com.github.junrar.rarfile.FileHeader; import com.github.junrar.volume.FileVolumeManager; import javafx.beans.property.SimpleStringProperty; import javafx.concurrent.Task; import org.apache.commons.io.IOUtils; import org.ini4j.Wini; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.math.BigDecimal; import java.math.RoundingMode; import java.net.URL; public class Downloader extends Task<Void> { private URL url; private String fileName; private Game game; public Downloader(URL url, String fileName, Game game) { this.url = url; this.fileName = fileName; this.game = game; } public class ProgressListener implements ActionListener { private double bytes = 0; private double mbDownloaded = 0; private double fileSize = 0; private double lastMB = 0; private long initialTime; private double speed = 0; private String downloadedText = ""; private String sizeText; public ProgressListener(double fileSize){ this.fileSize = fileSize; initialTime = System.nanoTime(); } @Override public void actionPerformed(ActionEvent e) { bytes = ((DownloadCountingOutputStream) e.getSource()).getByteCount(); updateProgress(bytes, fileSize); mbDownloaded = round(bytes/1e+6, 2); if(fileSize >= 1073741824){ //>= 1GB double temp = ((fileSize/1e+6)/1024); sizeText = round(temp,2) + " GB"; }else { double temp = (fileSize/1e+6); sizeText = round(temp,2) + " MB"; } if(mbDownloaded >= 1024){ downloadedText = String.valueOf(round(mbDownloaded/1024,2)); }else{ downloadedText = String.valueOf(mbDownloaded); } if((System.nanoTime() - initialTime) >= (Math.pow(10, 9))){ speed = round((mbDownloaded - lastMB), 3); initialTime = System.nanoTime(); lastMB = mbDownloaded; } updateMessage(String.valueOf(speed)+"MB/s,"+String.valueOf(downloadedText + "/" + sizeText)); } } @Override protected Void call() throws Exception { URL dl = this.url; File fl = null; String x = null; OutputStream os = null; InputStream is = null; try { updateMessage("Searching files...,---/---"); fl = new File(Settings.getInstallPath() +"/"+ this.fileName); os = new FileOutputStream(fl); is = dl.openStream(); DownloadCountingOutputStream dcount = new DownloadCountingOutputStream(os); double fileSize = Double.valueOf(dl.openConnection().getHeaderField("Content-Length")); ProgressListener progressListener = new ProgressListener(fileSize); dcount.setListener(progressListener); IOUtils.copy(is, dcount, 512000 ); updateMessage("Concluding...,Almost finished"); } catch (Exception e) { System.out.println(e); IOUtils.closeQuietly(os); IOUtils.closeQuietly(is); updateMessage(","); this.cancel(true); return null; } finally { IOUtils.closeQuietly(os); IOUtils.closeQuietly(is); updateMessage(","); updateProgress(0, 0); this.cancel(true); return null; } } protected static double round(double value, int places) { if (places < 0) throw new IllegalArgumentException(); BigDecimal bd = new BigDecimal(Double.toString(value)); bd = bd.setScale(places, RoundingMode.HALF_UP); return bd.doubleValue(); } }
DownloadCountingOutputStream类代码
import java.awt.event.ActionListener; import java.io.IOException; import java.io.OutputStream; public class DownloadCountingOutputStream extends CountingOutputStream { private ActionListener listener = null; public DownloadCountingOutputStream(OutputStream out) { super(out); } public void setListener(ActionListener listener) { this.listener = listener; } @Override protected void afterWrite(int n) throws IOException { super.afterWrite(n); if (listener != null) { listener.actionPerformed(new ActionEvent(this, 0, null)); } } }
优化方案
1. 减少进度回调频率
当前afterWrite每次写入字节都会触发回调,频繁的UI更新和计算会占用大量CPU资源。修改DownloadCountingOutputStream,累计写入一定字节(比如100KB)后再触发回调:
import java.awt.event.ActionListener; import java.io.IOException; import java.io.OutputStream; public class DownloadCountingOutputStream extends CountingOutputStream { private ActionListener listener = null; private long threshold = 102400; // 100KB private long accumulated = 0; public DownloadCountingOutputStream(OutputStream out) { super(out); } public void setListener(ActionListener listener) { this.listener = listener; } @Override protected void afterWrite(int n) throws IOException { super.afterWrite(n); accumulated += n; if (listener != null && accumulated >= threshold) { listener.actionPerformed(new ActionEvent(this, 0, null)); accumulated = 0; } } // 下载结束时手动触发一次,确保进度更新到100% public void flushProgress() { if (listener != null) { listener.actionPerformed(new ActionEvent(this, 0, null)); } } }
同时在IOUtils.copy完成后调用dcount.flushProgress()。
2. 复用HTTP连接
避免两次建立连接,先获取连接再读取输入流和文件大小:
@Override protected Void call() throws Exception { URL dl = this.url; File fl = null; OutputStream os = null; InputStream is = null; URLConnection conn = null; try { updateMessage("Searching files...,---/---"); fl = new File(Settings.getInstallPath() +"/"+ this.fileName); // 用BufferedOutputStream优化文件写入 os = new BufferedOutputStream(new FileOutputStream(fl)); conn = dl.openConnection(); conn.setConnectTimeout(5000); // 5秒连接超时 conn.setReadTimeout(10000); // 10秒读取超时 double fileSize = Double.valueOf(conn.getHeaderField("Content-Length")); is = conn.getInputStream(); DownloadCountingOutputStream dcount = new DownloadCountingOutputStream(os); ProgressListener progressListener = new ProgressListener(fileSize); dcount.setListener(progressListener); // 增大缓冲区到1MB IOUtils.copy(is, dcount, 1048576); dcount.flushProgress(); // 刷新最终进度 updateMessage("Concluding...,Almost finished"); } catch (Exception e) { System.out.println(e); IOUtils.closeQuietly(os); IOUtils.closeQuietly(is); updateMessage(","); this.cancel(true); return null; } finally { IOUtils.closeQuietly(os); IOUtils.closeQuietly(is); updateMessage(","); updateProgress(0, 0); this.cancel(true); return null; } }
3. 其他优化点
- 检查是否有防火墙或代理限制Java程序的网络速度;
- 尝试启用HTTP/2(如果服务器支持),可以使用OkHttp等库替代原生URLConnection,提升网络传输效率。
内容的提问来源于stack exchange,提问作者Mattia Mc Roll
相关产品推荐
相关产品推荐

