断点下载 多线程+HttpClient

断点下载, httpclient get请求 多线程方式

最近公司对接一个项目,有一个断点下载的接口使用,百度搜了一下,都是说使用多线程,找了一个demo, 是用URL 的方式下载的,接口定义是使用get请求,然后再请求头中放入 Range: "0-999"表示要下载的开头和结尾,返回的响应头中,Content-Range: 0-999/18845 表示此次下载文件的大小,和文件的总大小信息
因为我在第一次文件下载之前不知道文件的大小信息,所以第一次先指定1000长度的文件,然后获得总文件大小之后再开启线程进行下载,封装一个工具类,代码如下

package com.example;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;

import java.io.*;

/**
 * @Auther: asus
 * @Date: 2019/3/28 10 16
 * @Description:
 */
public class ThreadDown {


    AuthTokenItem authTokenItem = new AuthTokenItem();

    /**
     * 下载地址
     */
    private String url;
    /**
     * 下载文件的保存路径
     */
    private String targePath;
    /**
     * 线程个数
     */
    private int threadCount;
    /**
     * 正在运行的线程数
     */
    private int runningThread;
    /**
     * 每个线程下载文件的区块大小
     */
    private long blockSize;

    public TreadDown(String url, String targePath, int threadCount) {
        this.url = url;
        this.targePath = targePath;
        this.threadCount = threadCount;
    }

    public void download(){


        CloseableHttpClient client = HttpClients.createDefault();
        CloseableHttpResponse response = null;

        downThread downThread1 = null;

        HttpGet method = new HttpGet(url);
        // 第一次下载1000长度的文件,获取响应头中的文件总长度后再使用多线程进行下载
        method.addHeader(new BasicHeader("Range", "0-999"));
        try {
            response = client.execute(method);
            // 获取返回的响应状态码
            int code = response.getStatusLine().getStatusCode();
            // 文件总大小
            int size = 0;
            // 部分资源请求 返回状态码 206
            if (code == 206){
            	// 这里调用的接口返回的直接是字节数组 可以直接写入流,存进文件
                HttpEntity entity = response.getEntity();
                byte[] bytes = EntityUtils.toByteArray(entity);
                InputStream result = new ByteArrayInputStream(bytes);
                BufferedInputStream in=null;
                BufferedOutputStream out=null;
                in=new BufferedInputStream(result);
                // 保存文件到指定的文件路径下
                out=new BufferedOutputStream(new FileOutputStream(targePath));
                int len=-1;
                byte[] b=new byte[1024];
                while((len=in.read(b))!=-1){
                    out.write(b,0,len);
                }
                in.close();
                out.close();
                // 获取响应头中的文件大小信息 Content-Range : 0-999/18845
                Header header = response.getFirstHeader("Content-Range");
                String value = header.getValue();
                // 拿到文件的总大小
                size = Integer.parseInt(value.substring(value.lastIndexOf("/") + 1));
                // 分配每个线程需要下载的长度
                blockSize = (size - 999)/threadCount;
                runningThread = threadCount;
            }
            for (int i = 1; i <= threadCount; i++) {
                // 线程下载的开始位置
                long startIndex = (i - 1) * blockSize + 1000;
                // 线程下载的结束位置
                long endIndex = i * blockSize + 999;
                if (i == threadCount) {
                    endIndex = size - 1;
                }
                // 创建并开启线程
                 new Thread(new downThread(startIndex, endIndex, tempUrl, i)).start();

            }


        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    private class downThread implements Runnable{

        /**
         * 下载开始位置
         */
        private long startIndex;
        /**
         * 下载结尾位置
         */
        private long endIndex;
         /**
		  * 存放临时的文件目录
   		  */
		private String tempUrl;
        /**
         * 线程id
         */
        private int threadId;

        public downThread(long startIndex, long endIndex, String tempUrl, int threadId) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.tempUrl = tempUrl;
            this.threadId = threadId;
        }

        @Override
        public void run() {

            int total = 0;
            //创建一个文件保存下载的位置
            File positionFile = new File( tempUrl + threadId   + ".txt");
            //判断该文件是否存在
            if (positionFile.exists() && positionFile.length() > 1000) {
                // 得到该文件的输入流
                FileInputStream fis = null;
                try {
                    fis = new FileInputStream(positionFile);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                BufferedReader br = new BufferedReader(new InputStreamReader(fis));
                // 上次下载的总大小
                int lastTotal = 0;
                try {
                    lastTotal = Integer.valueOf(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                startIndex += lastTotal;
                total += lastTotal;
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }


            CloseableHttpClient client = HttpClients.createDefault();
            CloseableHttpResponse response;

            HttpGet method = new HttpGet(url);
            // 请求头中重新放入请求文件的开头和结尾位置
            method.addHeader(new BasicHeader("Range", startIndex + "-" + endIndex));

            int code = 0;
            try {
                response = client.execute(method);
                code = response.getStatusLine().getStatusCode();
                HttpEntity entity = response.getEntity();

                if (code == 206){
                    byte[] bytes = EntityUtils.toByteArray(entity);
                    InputStream in = new ByteArrayInputStream(bytes);
                    // 创建一个新的RandomAccessFile 对象
                    RandomAccessFile raf = new RandomAccessFile(targePath, "rw");
                    // 从指定位置开始下载
                    raf.seek(startIndex);
                    int len = 0;
                    // 缓存区设置越大,下载速度越快
                    byte[] buffer = new byte[1024];
                    while ((len = in.read(buffer)) != -1) {
                        raf.write(buffer, 0, len);
                        //创建一个同步更新到存储设备的RandomAccessFile对象
                        RandomAccessFile file = new RandomAccessFile(   positionFile, "rwd");
                        total+=len;
                        //必须将total转化为字节
                        file.write(String.valueOf(total).getBytes());
                        file.close();
                    }
                    raf.close();
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                System.out.println("线程" + threadId +
                        "下载长度" + startIndex + "--" + endIndex +
                        "请求code" + code);
                 // 线程运行完后,删除临时文件
                synchronized(TreadDown.class){
                    runningThread--;
                    if(runningThread<1){
                        for(int i=1;i<=threadCount;i++){
                            File file =new File(tempUrl+i+".txt");
                            //删除成功,返回true
                            file.delete();
                        }
                    }
                }
            }
        }
    }

}

这里我们用到了一个RandomAccessFile 类,其它博客中也有比较详细的解说,简单来讲,RandomAccessFile 直接继承于Object类,可以从文件的任意位置进行操作,使用seek() 方法。

然后就是工具类方法的调用了

package com.example;

/**
 * @Auther: asus
 * @Date: 2019/3/28 11 50
 * @Description:
 */
public class TestDownThread {

    public static void main(String[] args) {

		/**
		 * get请求的完整url, 如有参数需要先拼凑
		 */
        String url = "";
        /**
         * 存放临时文件的临时目录
         */
        String tempUrl = "";
        /**
         * 下载文件的完整保存路径 我这里下载的是zip文件
         */
        String targePath = "E:/test.zip";
        /** 
         * 要使用的线程个数
         */
        int threadCount = 5;

        TreadDown td = new TreadDown(url,tempUrl,targePath,threadCount);
        td.download();

    }
}

执行结果
在这里插入图片描述

如有不当,还请指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值