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

说明

用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

  • 3
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值