多线程案例---多线程下载文件

基本逻辑:
使用scheduledExecutorService.scheduleAtFixedRate()方法启动一线程每延迟1秒统计1次下载信息
文件切分多块,每块用一个线程下载,全部下载完成后合并文件切分的多个临时文件,合并完成后删除临时文件
代码:
业务主类:

/**
 * @author YongXin
 * @date Created in 2022/10/2 10:37
 */
public class Downloader {

    private static final Logger log = LoggerFactory.getLogger(Downloader.class);

    public ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

    //创建线程池对象
    public ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(DownloaderConstant.THREAD_NUM, DownloaderConstant.THREAD_NUM, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));

    private CountDownLatch countDownLatch = new CountDownLatch(DownloaderConstant.THREAD_NUM);

    public void download(String downloadUrl){
        HttpConnection connection = HttpConnection.create(downloadUrl, Proxy.NO_PROXY);
        HttpURLConnection httpURLConnection = connection.getHttpURLConnection();
        String fileName = FileUtil.getName(downloadUrl);
        fileName = "D:\\" + fileName;
        //本地文件大小
        long localFileLength = FileUtil.size(new File(fileName));
        //下载文件总大小
        int contentLength = httpURLConnection.getContentLength();

        //判断文件是否已下载完毕
        if (localFileLength >= contentLength) {
            log.info("文件已下载完毕" + fileName);
            return;
        }
        //创建获取任务信息的下载对象
        DownloaderInfoThread downloaderInfoThread = new DownloaderInfoThread(contentLength);
        //将任务交给线程执行,每隔一秒执行一次
        scheduledExecutorService.scheduleAtFixedRate(downloaderInfoThread, 1, 1, TimeUnit.SECONDS);

        //切分任务下载
        ArrayList<Future> futures = new ArrayList<>();
        split(downloadUrl, futures, httpURLConnection);

        try {
            countDownLatch.await();//所有线程执行完毕,计数器置0后才继续执行下边代码
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //合并文件
        boolean merge = merge(fileName);
        if (merge) {
            //清楚临时文件
            clearTemp(fileName);
        }
        System.out.println("下载完成");
        connection.disconnect();
        scheduledExecutorService.shutdownNow();
        //关闭线程池
        threadPoolExecutor.shutdown();
    }

    /**
     * 文件切分
     *
     * @param url
     * @param futureList
     */
    public void split(String url, ArrayList<Future> futureList, HttpURLConnection httpURLConnection) {
        //获取下载文件的大小
        long contentLength = httpURLConnection.getContentLength();
        //计算切分后的文件大小
        long size = contentLength / DownloaderConstant.THREAD_NUM;
        //计算分块个数
        for (int i = 0; i < DownloaderConstant.THREAD_NUM; i++) {
            //计算下载起始位置
            long startPos = i * size;
            //计算下载结束位置
            long endPos;

            if (i == DownloaderConstant.THREAD_NUM - 1) {
                //最后一块,不指定结束位置,下载剩余全部
                endPos = 0;
            } else {
                endPos = startPos + size;
            }
            //如果不是第一块,起始位置要加一
            if (startPos != 0) {
                startPos += 1;
            }
            //创建任务
            DownloaderTask downloaderTask = new DownloaderTask(url, startPos, endPos, i,countDownLatch);
            //将任务提交到线程池当中
            Future<Boolean> future = threadPoolExecutor.submit(downloaderTask);

            futureList.add(future);

        }
    }

    /**
     * 文件合并
     * @param fileName
     * @return
     */
    public boolean merge(String fileName) {
        log.info("开始合并");
        byte[] buffer = new byte[1024 * 1000];
        int len = -1;
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "rw")) {
            for (int i = 0; i < DownloaderConstant.THREAD_NUM; i++) {
                try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(fileName + ".temp" + i))) {
                    while ((len = bufferedInputStream.read(buffer)) != -1) {
                        randomAccessFile.read(buffer, 0, len);
                    }
                }
            }
            log.info("文件合并完毕");
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 清空临时文件
     * @param fileName
     * @return
     */
    public boolean clearTemp(String fileName){
        for (int i = 0; i < DownloaderConstant.THREAD_NUM; i++) {
            File file = new File(fileName + ".temp" + i);
            file.delete();
        }
        return true;
    }
}

下载信息线程类:

/**
 * @author YongXin
 * @date Created in 2022/10/2 11:44
 */
public class DownloaderInfoThread implements Runnable{

    //下载文件总大小
    private long httpFileContentLength;

    //本地已下载文件大小
    public static LongAdder finishedSize = new LongAdder();

    //本次累计下载大小
    public static volatile LongAdder downSize = new LongAdder();

    //前一次累计下载的大小
    public double prevSize;
    //downSize - prevSize = 这一秒下载大小

    public DownloaderInfoThread(long httpFileContentLength) {
        this.httpFileContentLength = httpFileContentLength;
    }


    @Override
    public void run() {
        //计算文件总大小  单位:MB
        String httpFileSize = String.format("%.2f", httpFileContentLength / DownloaderConstant.MB);

        //计算每秒下载速度
        int speed = (int)((downSize.doubleValue() - prevSize) / 1024d);
        prevSize = downSize.doubleValue();

        //计算剩余文件大小
        double remainSize = httpFileContentLength - finishedSize.doubleValue() -downSize.doubleValue();

        //计算剩余时间
        String remainTime = String.format("%.1f", remainSize / 1024d / speed);
        if ("Infinitys".equalsIgnoreCase(remainTime)){
            remainTime = "-";
        }

        //已下载大小
        String currentFileSize = String.format("%.2f", (downSize.doubleValue() - finishedSize.doubleValue()) / DownloaderConstant.MB);

        String downInfo = String.format("已下载 %smb/%smb,速度 %skb/s,剩余时间 %ss", currentFileSize, httpFileSize, speed, remainTime);
        System.out.println(downInfo);
    }
}

切分下载线程类:

/**
 * @author YongXin
 * @date Created in 2022/10/3 10:19
 */
public class DownloaderTask implements Callable<Boolean> {

    public static final Logger log = LoggerFactory.getLogger(DownloaderTask.class);

    //下载链接
    private String url;
    //下载起始位置
    private long startPos;
    //下载结束位置
    private long endPos;
    //标识当前是哪一部分的
    private int part;

    private CountDownLatch countDownLatch;

    public DownloaderTask(String url, long startPos, long endPos, int part, CountDownLatch countDownLatch) {
        this.url = url;
        this.startPos = startPos;
        this.endPos = endPos;
        this.part = part;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public Boolean call() throws Exception {
        //下载文件名
        String fileName = FileUtil.getName(url);
        //分块文件名
        fileName = fileName + ".temp" + part;
        //下载路径
        String fileNamePath = DownloaderConstant.PATH + fileName;

        //获取分块下载的链接
        HttpConnection connection = HttpConnection.create(url, Proxy.NO_PROXY);
        HttpURLConnection httpURLConnection = connection.getHttpURLConnection();
        log.info("下载的区间是:"+startPos+"-"+endPos);
        if (endPos != 0) {
            httpURLConnection.setRequestProperty("RANGE", "bytes=" + startPos + "-" + endPos);
        } else {
            httpURLConnection.setRequestProperty("RANGE", "bytes=" + startPos + "-");
        }

        try (
                InputStream inputStream = httpURLConnection.getInputStream();
                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                RandomAccessFile accessFile = new RandomAccessFile(fileNamePath, "rw");
        ) {
            byte[] buffer = new byte[1024 * 1000];
            int len = -1;
            while ((len = bufferedInputStream.read(buffer)) != -1){
                //1秒内下载数据之和,通过原子类进行操作
                DownloaderInfoThread.downSize.add(len);
                accessFile.read(buffer,0,len);
            }


        } catch (FileNotFoundException e) {
            log.error("文件找不到");
            return false;
        } catch (Exception e) {
            log.error("文件下载失败");
            return false;
        }finally {
            httpURLConnection.disconnect();
            countDownLatch.countDown();
        }
        return true;
    }
}

枚举类:

/**
 * @author YongXin
 * @date Created in 2022/10/3 10:25
 */
public class DownloaderConstant {
    //下载地址
    public static final String PATH = "D:\\";
    //兆
    public static final Double MB = 1024d*1024d;

    //线程数量
    public static final int THREAD_NUM = 5;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LeBron永鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值