当前位置: 首页 > news >正文

Java多线程分块下载文件

目录

技术原理

完整代码实现

 主程序类(Test.java)

下载线程类(DownThread.java)

关键代码解析

1. 分块算法解析

2. HTTP Range请求头

 3. RandomAccessFile的使用

运行结果


技术原理

        多线程下载的核心思想是:将一个大文件分成若干块,每个线程负责下载其中一块,最后将所有块合并成完整文件。其关键技术点包括:

  1. 文件分割算法:将文件总大小平均分配给每个线程,计算每个线程负责下载的起始和结束位置。

  2. HTTP Range 请求:通过 HTTP 协议的Range头字段,告知服务器只返回文件的特定片段(从 start 到 end 的字节范围)。

  3. 随机文件写入:使用RandomAccessFile类,支持线程将下载的片段写入文件的指定位置,避免线程间的写入冲突。

  4. 多线程协同:多个线程并行工作,各自完成分配的下载任务。

完整代码实现

 主程序类(Test.java)

package com.splitfile;import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;/*** 多线程分段下载文件的主程序* 功能:将一个网络文件分割成多个部分,使用指定数量的线程并行下载*/
public class Test {/*** savePath 本地保存路径(包含文件名)* fileUrl 目标文件的网络URL* threadNum 下载线程数量*/public static void downFile(String savePath, String fileUrl, int threadNum) {// 创建本地文件及父目录File targetFile = new File(savePath);if (!targetFile.getParentFile().exists()) {targetFile.getParentFile().mkdirs(); // 确保父目录存在}try {// 创建URL对象并打开连接URL url = new URL(fileUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();// 设置连接超时时间(30秒)conn.setConnectTimeout(30000);// 设置请求方式为GETconn.setRequestMethod("GET");// 检查连接是否成功(响应码200表示成功)if (conn.getResponseCode() == 200) {System.out.println("连接到文件服务器成功!");// 获取文件总大小(单位:字节)int fileSize = conn.getContentLength();System.out.println("文件总大小:" + fileSize + " 字节");// 计算每个线程需要下载的字节块大小// 算法:(总大小 + 线程数 - 1) / 线程数 → 实现向上取整,避免最后一个线程分配过多int blockSize = (fileSize + threadNum - 1) / threadNum; System.out.println("每个线程下载块大小:" + blockSize + " 字节");// 启动指定数量的下载线程for (int i = 0; i < threadNum; i++) {new DownThread(blockSize, i, url, targetFile,fileSize).start();}} else {System.out.println("连接失败,响应码:" + conn.getResponseCode());}} catch (MalformedURLException e) {System.out.println("URL格式错误:" + e.getMessage());e.printStackTrace();} catch (IOException e) {System.out.println("网络IO异常:" + e.getMessage());e.printStackTrace();}}public static void main(String[] args) {// 测试下载:保存路径、文件URL、线程数量downFile("./mp3/a.mp3", "http://127.0.0.1:8099/audio/relax1.mp3", 3);}
}

下载线程类(DownThread.java)

package com.splitfile;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;public class DownThread extends Thread {private int blockSize;    // 每个线程的下载块大小private int threadIndex;  // 线程索引(0开始)private URL fileUrl;      // 目标文件的URLprivate File targetFile;  // 本地保存的文件private int fileSize;     // 文件大小private int startPoint;   // 本线程下载的起始位置(字节)private int endPoint;     // 本线程下载的结束位置(字节)/*** blockSize 每个线程的下载块大小* threadIndex 线程索引* url 目标文件URL* file 本地保存文件*/public DownThread(int blockSize, int threadIndex, URL url, File file, int fileSize) {this.blockSize = blockSize;this.threadIndex = threadIndex;this.fileUrl = url;this.targetFile = file;this.fileSize = fileSize;// 计算每个线程的起始下载位置this.startPoint = blockSize * threadIndex;// 计算每个线程的结束下载位置,因为blockSize是向上取整得到的,所以最后一个线程取小的字节数下载this.endPoint = Math.min(blockSize * (threadIndex + 1) - 1, fileSize - 1);}/*** 线程执行体:下载指定范围的文件片段*/@Overridepublic void run() {// 打印当前线程的下载范围System.out.println(Thread.currentThread().getName() + " 下载范围:" + startPoint + " - " + endPoint + " 字节");RandomAccessFile raf = null;InputStream in = null;try {// 打开与服务器的连接HttpURLConnection conn = (HttpURLConnection) fileUrl.openConnection();conn.setConnectTimeout(30000);conn.setRequestMethod("GET");// 设置Range请求头:告知服务器只返回指定范围的字节conn.setRequestProperty("Range", "bytes=" + startPoint + "-" + endPoint);// 服务器返回206表示部分内容请求成功(Range生效)if (conn.getResponseCode() == 206) {// 获取输入流(仅包含指定范围的文件内容)in = conn.getInputStream();// 创建RandomAccessFile用于随机写入文件raf = new RandomAccessFile(targetFile, "rw");// 移动文件指针到本线程的起始位置raf.seek(startPoint);// 缓冲区:提高读写效率byte[] buffer = new byte[1024];int len;// 读取数据并写入文件while ((len = in.read(buffer)) != -1) {raf.write(buffer, 0, len);}System.out.println(Thread.currentThread().getName() + " 下载完成!");} else {System.out.println(Thread.currentThread().getName() + " 范围请求失败,响应码:" + conn.getResponseCode());}} catch (IOException e) {System.out.println(Thread.currentThread().getName() + " 下载异常:" + e.getMessage());e.printStackTrace();} finally {// 关闭资源try {if (in != null) in.close();if (raf != null) raf.close();} catch (IOException e) {e.printStackTrace();}}}
}

关键代码解析

1. 分块算法解析

int block = (fileSize + threadNum - 1) / threadNum;
  • 这是向上取整算法
  • 确保所有块的大小总和能够覆盖整个文件
  • 例如:文件100字节,3个线程 → (100+3-1)/3 = 34字节/块

2. HTTP Range请求头

conn.setRequestProperty("Range", "bytes=" + startPoint + "-" + endPoint);
  • 这是分段下载的核心,通过 HTTP 的Range头告诉服务器只返回指定字节范围的数据。
  • 服务器成功响应时会返回 206 状态码(部分内容)。

 3. RandomAccessFile的使用

raf = new RandomAccessFile(targetFile, "rw");
raf.seek(startPoint);  // 移动到起始位置
raf.write(buffer, 0, len);  // 写入数据
  • RandomAccessFile支持通过seek()方法定位到文件的任意位置。
  • 多个线程可以同时写入同一个文件的不同位置,确保每个线程写入的数据不会覆盖其他线程的内容。

运行结果


文章转载自:

http://5trpjLfe.wgbmj.cn
http://BzE5kdzn.wgbmj.cn
http://n1HzvXxA.wgbmj.cn
http://IO5V5XBb.wgbmj.cn
http://YzeFs7un.wgbmj.cn
http://WXF86V7P.wgbmj.cn
http://X0HsyglL.wgbmj.cn
http://8qNHKSpD.wgbmj.cn
http://hqiLMXg0.wgbmj.cn
http://QTtp9YyF.wgbmj.cn
http://9fVChOYk.wgbmj.cn
http://rfwsTmzI.wgbmj.cn
http://vEoetfMB.wgbmj.cn
http://BD1uwaHW.wgbmj.cn
http://xHetVhDM.wgbmj.cn
http://JzGdFybU.wgbmj.cn
http://ETx6Ejye.wgbmj.cn
http://H15YJkG9.wgbmj.cn
http://6HFxaIPj.wgbmj.cn
http://CVadMZfe.wgbmj.cn
http://yQFf1bC8.wgbmj.cn
http://cxBXFX8h.wgbmj.cn
http://zAmGULHk.wgbmj.cn
http://MAb9tYYa.wgbmj.cn
http://iGsGEf5Q.wgbmj.cn
http://MDZ12xhb.wgbmj.cn
http://UjT4OSdq.wgbmj.cn
http://CNEyAPYu.wgbmj.cn
http://rMU6xka3.wgbmj.cn
http://SluFv51E.wgbmj.cn
http://www.dtcms.com/a/380785.html

相关文章:

  • 玩转deepseek之自动出试卷可直接导出word
  • 智慧城管源码,java版城管综合执法监督系统微服务源码
  • FastAPI学习(四)——中间件
  • 维特imu ros2-foxy
  • Dubbo2 与 Dubbo3 的主要区别与演进
  • 【二分查找】
  • 新疆移动中兴B862AV3.1-M2_晨星mso9385_uwe5621ds_优盘免拆卡刷固件包
  • C++:map容器
  • Java内存模型与线程私有共享区域与直接内存的理解
  • MCP专题五、MCP 的未来趋势与展望
  • SIFT特征匹配实战:KNN算法实现指纹认证
  • ETL 不只是数据搬运工:如何实现智能转换与清洗?
  • UDP套接字的使用
  • 【Vue2手录11】Vue脚手架(@vue_cli)详解(环境搭建+项目开发示例)
  • Vue 使用docx-preview,渲染word后,继续其他操作(word中内容相关)的实现
  • [优选算法专题二——NO.16最小覆盖子串]
  • Nginx生产级优化配置全解析和配置原因解析
  • 14自由度汽车动力学模型
  • FS950R08A6P2B 双通道汽车级IGBT模块Infineon英飞凌 电子元器件核心解析
  • 交换机协议栈FRR中使用
  • C++ 二叉搜索树的详解与实现
  • 记录:离线部署
  • python逆向-逆向pyinstaller打包的exe程序反编译获取源代码
  • 最大连续 1 的个数
  • LVS负载均衡群集和LVS+Keepalived群集
  • 嵌入式开发:中断配置全解析
  • 【Vue3】07-利用setup编写vue(2)-setup的语法糖
  • 使用 信号量(Semaphore) 来控制异步任务并发数
  • 1688 商品 API 实战指南:B2B 场景下的合规对接与批量运营方案
  • Qt Bridge for Figma