多線程下載利用 HTTP/1.1 的 Range 請求頭實現分塊並行傳輸,配合 RandomAccessFile 的隨機讀寫能力,可顯著提升大文件下載速度並支持斷點續傳。本文通過完整代碼示例,講解核心步驟與關鍵細節。
Content-Length,用 RandomAccessFile.setLength() 在本地創建同等大小空文件。Range: bytes=start-end,響應碼 206 Partial Content 表示成功,通過 seek(start) 定位寫入。package cn.me.test;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class MultiThreadDownload {
public static void main(String[] args) throws Exception {
// 1. 目標文件與下載地址
String fileName = "aa.rar";
String urlStr = "https://localhost:7777/day18";
// 2. 建立連接獲取文件長度
URL url = new URL(urlStr + "/" + fileName);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
int length = con.getContentLength(); // 關鍵:獲取總字節數
// 3. 預創建同等大小文件佔位
RandomAccessFile file = new RandomAccessFile("d:/a/" + fileName, "rw");
file.setLength(length);
file.close();
// 4. 線程數與分塊計算
int threadCount = 3;
int block = length % threadCount == 0 ? length / threadCount : length / threadCount + 1;
System.err.println("每個線程下載:" + block + " 字節");
// 5. 啟動線程
for (int i = 0; i < threadCount; i++) {
int start = i * block;
int end = Math.min(start + block - 1, length - 1); // 修正邊界
System.err.println("線程 " + i + ": " + start + " - " + end);
new DownloadThread(fileName, start, end, url).start();
}
}
static class DownloadThread extends Thread {
private final String fileName;
private final int start;
private final int end;
private final URL url;
public DownloadThread(String fileName, int start, int end, URL url) {
this.fileName = fileName;
this.start = start;
this.end = end;
this.url = url;
}
@Override
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// 關鍵:設置分段範圍
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
if (conn.getResponseCode() == 206) { // 206 = Partial Content
try (InputStream in = conn.getInputStream();
RandomAccessFile out = new RandomAccessFile("d:/a/" + fileName, "rw")) {
out.seek(start); // 定位寫入位置
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
}
System.err.println(getName() + " 完成");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
允許在文件任意位置讀寫,seek(pos) 移動文件指針,是分塊寫入的基石。
bytes=0-499:前 500 字節bytes=500-:第 500 字節到結尾(斷點續傳常用)Accept-Ranges: bytes 並返回 206 狀態碼原代碼 end = start + block - 1 可能導致最後一線程越界,建議用 Math.min(start + block - 1, length - 1)。
使用 try-with-resources 自動關閉流,避免洩漏;生產環境應加入進度持久化、校驗和重試機制。
start/end/currentPos 序列化保存,恢復時讀取繼續。掌握多線程下載原理,不僅能實現高效下載器,更能深入理解 HTTP 協議、NIO 與並發編程的結合應用。