用java实现文件的断点续传并发下载

本文介绍使用Java实现文件的断点续传方法,利用HTTP的RANGE首部字段进行分段下载,通过多线程并发提高下载效率,并提供源码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说明

用java实现文件的断点续传,使用了HTTP的首部字段实现,在网上看到例子,手动实现一遍,理解其原理,在这记录下

正文

要实现断点续传,要在请求中设置请求开始的位置和结束位置,在HTTP请求中设置RANGE首部字段,之后服务器如果能正常返回,返回206状态码
用java实现的关键点:
1.设置请求的首部字段,使用java的net包
2.在读取资源文件后,要保存文件,从断点处保存,使用RandAccessFile类
3.使用多线程并发的方式进行,如何正确设置起始位置

主要思路就是:
1. 设置文件信息,包括文件所在的URL,文件名,文件保存的路径及文件需要分段下载的次数
2. 下载时,先连接服务器,得到文件的大小,通过服务器响应的首部字段Content-Length获得,得到文件大小后,根据分段下载的次数设置每次开始的位置,结束位置。并创造一个信息临时文件,用来保存每次分段下载的起始位置,用于非第一次下载时,可以直接本地读取起始信息
3. 分段下载根据开始位置,保存在下载文件的合适位置,使用RandAccessFile类的seek()方法定位
4.

1、设置首部字段

在分段抓取的类中,根据分段次数,设置开始位置

URL ourl = new URL(url);
HttpURLConnection httpConnection = (HttpURLConnection) ourl.openConnection();
String prop = "bytes=" + startPos + "-";
httpConnection.setRequestProperty("RANGE", prop); //设置请求首部字段 RANGE

分段抓取代码

package action;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import util.FileUtil;
import util.LogUtil;

/**
 * 用于分段传输
 * 使用HTTP协议的首部字段实现
 * @author wds
 *
 */
public class FileSplitFetch implements Runnable{

    protected String url;               // 文件所在url
    protected long startPos;            // 分段传输的开始位置
    protected long endPos;              // 结束位置
    protected int threadID;             // 线程编号
    protected boolean downOver = false; // 下载完成标志
    protected boolean stop = false;     // 当前分段结束标志
    FileUtil fileUtil = null;           // 文件工具

    public FileSplitFetch(String url, long startPos, long endPos, int threadID, String fileName) throws IOException {
        super();
        this.url = url;
        this.startPos = startPos;
        this.endPos = endPos;
        this.threadID = threadID;
        fileUtil = new FileUtil(fileName, startPos);
    }


    @Override
    public void run() {
        while(startPos < endPos && !stop){
            try {
                URL ourl = new URL(url);
                HttpURLConnection httpConnection = (HttpURLConnection) ourl.openConnection();
                String prop = "bytes=" + startPos + "-";
                httpConnection.setRequestProperty("RANGE", prop); //设置请求首部字段 RANGE

                LogUtil.log(prop);

                InputStream input = httpConnection.getInputStream(); 
                byte[] b = new byte[1024];
                int bytes = 0;
                while((bytes = input.read(b)) > 0 && startPos < endPos && !stop){
                    startPos += fileUtil.write(b, 0, bytes);
                }

                LogUtil.log("Thread" + threadID + " is done");
                downOver = true;
            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } 
        }
    }

    /**
     * 打印响应的头部信息
     * @param conn
     */
    public void printResponseHeader(HttpURLConnection conn){
        for(int i = 0; ; i++){
            String fieldsName = conn.getHeaderFieldKey(i);
            if(fieldsName != null){
                LogUtil.log(fieldsName + ":" + conn.getHeaderField(fieldsName));
            }else{
                break;
            }
        }
    }

    /**
     * 停止分段传输
     */
    public void setSplitTransStop(){
        stop = true;
    }



}

2、文件的保存

在文件保存时,要根据开始位置保存在下载文件的适当位置,使用RandomAccessFile的seek()方法

public class FileUtil{

    private RandomAccessFile file; 
    private long startPos; // 文件存储的起始位置

    public FileUtil(String fileName, long startPos) throws IOException{
        file = new RandomAccessFile(fileName, "rw");
        this.startPos = startPos;
        file.seek(startPos);
    }

    public synchronized int write(byte[] data, int start, int len){
        int res = -1;
        try {
            file.write(data, start, len);
            res = len;
        } catch (IOException e) {
            LogUtil.log(e.getMessage());
            e.printStackTrace();
        }
        return res;
    }

}

3、并发下载

在下载文件时,可以使用多个子线程并发进行,这里使用了一个数组保存多个线程

/**
     * 开始下载文件
     * 1. 获取文件长度
     * 2. 分割文件
     * 3. 实例化分段下载子线程
     * 4. 启动子线程
     * 5. 等待子线程的返回
     * @throws IOException 
     */
    public void startDown(){

        if(firstDown){
            fileLen = getFileSize();
            if(fileLen == -1){
                LogUtil.log("文件大小未知");
                return;
            }else if(fileLen == -2){
                LogUtil.log("文件不可访问");
                return;
            }
            else{

                // 设置每次分段下载的开始位置
                for(int i = 0; i < startPos.length; i++){
                    startPos[i] = i * (fileLen / startPos.length);
                }

                //设置每次分段下载的结束位置
                for(int i = 0; i < endPos.length - 1; i++){
                    endPos[i] = startPos[i + 1];
                }
                endPos[endPos.length - 1] = fileLen;

            }

        }

        //启动分段下载子线程

        try {
                fileSplitFetchs = new FileSplitFetch[startPos.length];
                for(int i = 0; i < startPos.length; i++){
                    System.out.println(startPos[i] + " " + endPos[i]);
                    fileSplitFetchs[i] = new FileSplitFetch(siteInfo.getUrl(), startPos[i], endPos[i], i, 
                            siteInfo.getFilePath() + File.separator + siteInfo.getFileName());
                    LogUtil.log("Threa " + i + ", start= " + startPos[i] + ",  end= " + endPos[i]);
                    new Thread(fileSplitFetchs[i]).start();
                }

                //保存文件下载信息
                saveInfo();
                //循环判断所有文件是否下载完毕
                boolean breakWhile = false;
                while(!stop){

                    LogUtil.sleep(500);
                    breakWhile = true;

                    for(int i = 0; i < startPos.length; i++){
                        if(! fileSplitFetchs[i].downOver){
                            breakWhile = false; // 还存在未下载完成的线程
                            break;
                        }
                    }

                    if(breakWhile)
                        break;
                }
        } catch (IOException e) {
            LogUtil.log(e.getMessage());
            e.printStackTrace();
        }

        LogUtil.log("文件下载完成");
    }

4、设置文件的基本信息

基本信息包含了文件所在站点信息,文件本地保存路径,文件名,文件分段下载次数

public class SiteInfo implements Serializable {

    private static final int SPLIT_COUNT = 5; // 默认次数为5次

    private String url;        // 文件所在站点的url
    private String filePath;   // 文件保存的路径
    private String fileName;   // 文件的名字
    private int splits;        // 分段下载文件的次数

    public SiteInfo(){
        this("","","",SPLIT_COUNT);
    }

    public SiteInfo(String url, String filePath, String fileName, int splits) {
        this.url = url;
        this.filePath = filePath;
        this.fileName = fileName;
        this.splits = splits;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public int getSplits() {
        return splits;
    }

    public void setSplits(int splits) {
        this.splits = splits;
    }

    public String getSimpleName(){
        String[] names = fileName.split("\\.");
        return names[0];
    }



}

源码地址:https://github.com/Edenwds/breakpointtrans
网上例子:https://github.com/Edenwds/breakpointtrans/blob/master/java%E5%AE%9E%E7%8E%B0%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0.txt

1.得到服务器下载文件的大小,然后在本地设置一个临时文件和服务器端文件大小一致 a)获得访问网络地址 b)通过URL对象的openConnection()方法打开连接,返回一个连接对象 c)设置请求头 i.setRequestMethod ii.setConnectTimeout iii.setReadTimeout d)判断是否响应成功 e)获取文件长度(getContentLength()) f)随机访问文件的读取与写入RandomAccessFile(file, mode) g)设置临时文件与服务器文件大小一致(setLength()) h)关闭临时文件 2.计算出每个线程下载的大小(开始位置,结束位置) a)计算出每个线程下载的大小 b)for循环,计算出每个线程的开始、结束位置 c)最后一个线程处理 3.每创建好一次就要开启线程下载 a)构造方法 b)通过URL对象的openConnection()方法打开连接,返回一个连接对象 c)设置请求头 i.setRequestMethod ii.setConnectTimeout d)判断是否响应成功(206) e)获取每个线程返回的流对象 f)随机访问文件的读取与写入RandomAccessFile(file, mode) g)指定开始位置 h)循环读取 i.保存每个线程下载位置 ii.记录每次下载位置 iii.关闭临时记录位置文件 iv.随机本地文件写入 v.记录已下载大小 i)关闭临时文件 j)关闭输入流 4.为了杀死线程还能继续下载的情况下,从本地文件上读取已经下载文件的开始位置 a)创建保存记录结束位置的文件 b)读取文件 c)将流转换为字符 d)获取记录位置 e)把记录位置赋给开始位置 5.当你的n个线程都下载完毕的时候我进行删除记录下载位置的缓存文件 a)线程下载完就减去 b)当没有正在运行的线程时切文件存在时删除文件
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值