断点续传就像 “下载电影时断网了,下次接着下” ,而 多线程 就是 “雇几个工人同时搬砖” ,两者的结合可以让大文件下载更快、更可靠。核心原理分三步:
一、文件分块(把大文件切成小份)
-
步骤:
- 把文件按固定大小(比如每块1MB)切成多个小块。
- 每个线程负责下载一个或多个块。
-
例子:
- 一个100MB的文件,切成100块(每块1MB)。
- 开10个线程,每个线程下载10块。
二、记录下载进度(记住每个工人搬了哪几块砖)
-
关键点:
- 进度文件:用一个临时文件或数据库记录每个块的下载状态(比如:块1下载了50%,块2下载完成)。
- 断点恢复:如果下载中断,重启时读取进度文件,只下载未完成的块。
-
例子:
- 下载到60%时断网 → 记录哪些块没下完 → 下次继续下这些块。
三、多线程下载 + 合并文件
-
多线程下载:
- 每个线程独立下载分配的块,互不干扰。
- 注意:每个线程需要知道下载的起始位置和结束位置。
// 示例:线程负责下载第start到第end字节的数据 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("Range", "bytes=" + start + "-" + end); -
合并文件:
- 所有块下载完成后,按顺序拼接成一个完整文件。
- 注意:确保每个块写入文件的正确位置。
四、代码核心流程(伪代码)
// 1. 初始化分块信息(假设文件总大小已知)
long fileSize = getFileSize();
int blockSize = 1 * 1024 * 1024; // 1MB
int blockCount = (int) (fileSize / blockSize) + 1;
// 2. 读取进度文件(如果有)
Map<Integer, Long> progressMap = readProgressFile();
// 3. 创建线程池,分配下载任务
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < blockCount; i++) {
int blockIndex = i;
executor.submit(() -> {
long start = blockIndex * blockSize;
long end = Math.min(start + blockSize - 1, fileSize - 1);
// 如果该块已下载完成,跳过
if (progressMap.containsKey(blockIndex) && progressMap.get(blockIndex) == end) {
return;
}
// 下载该块(支持断点续传)
downloadBlock(url, start, end, blockIndex);
// 更新进度文件
updateProgress(blockIndex, end);
});
}
// 4. 等待所有线程完成,合并文件
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
mergeBlocksToFile();
五、关键问题与解决方案
| 问题 | 解决方案 |
|---|---|
| 如何分块? | 根据文件大小和线程数动态计算块大小(比如100MB文件,10线程 → 每块10MB)。 |
| 如何记录进度? | 用临时文件或数据库记录每个块的下载状态(如 块号:已下载的字节位置)。 |
| 下载中断如何处理? | 重启时读取进度文件,继续下载未完成的块。 |
| 多线程写文件冲突? | 每个线程下载到临时文件的不同位置(如 file.part1, file.part2),最后合并。 |
| 网络错误重试? | 为每个块设置重试机制(比如最多重试3次)。 |
六、总结
多线程断点续传的核心思想:
- 分而治之:大文件拆小块,多线程并行下载。
- 记录进度:实时保存每个块的状态,支持断点恢复。
- 合并结果:所有块下载完成后拼成完整文件。
口诀:
「大文件,切成块,多线程来加速快
记录进度不怕断,下次接着继续干
分块下载省时间,合并文件就圆满!」