• java实现多线程下载器


    前言
    👏作者简介:我是笑霸final,一名热爱技术的在校学生。
    📝个人主页:个人主页1 || 笑霸final的主页2
    📕系列专栏:项目专栏
    📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
    🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏

    一、实现单线程下载器

    1.1环境准备

    开发工具:IDEA
    JDK版本:8
    编码:utf-8
    下载的资源:https://profile-avatar.csdnimg.cn/2fe9f00ded33433385db6b55fc7a4195_weixin_52062043.jpg
    我们以我的头像为例

    项目目录
    在这里插入图片描述

    Constant:存放常量的类
    HttpUtils:http工具包
    Downloader:下载器
    Main:主类

    1.2文件的下载

    类似于我们本地文件 从一个磁盘复制到另外一个磁盘。
    对于互联网的下载,我们需要将本地和下载服务器建立链接
    在这里插入图片描述

    1.3 主类Main 的实现

    public class Main {
        public static void main(String[] args) {
            String url = null;
            Scanner scanner = new Scanner(System.in);
            if (args == null || args.length<1){
               while (url==null || " ".equals(url)){
                   url = scanner.nextLine();
               }
            }else{
                url = args[0];
            }
            System.out.println("你输入的网址是: "+url);
            System.out.println("开始下载");
            Downloader downloader = new Downloader();
            downloader.download(url);
            System.out.println("下载完毕");
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这里的代码应该就没有多说的了 都很简单,接下来我们就应该去写Downloader的代码了

    1.4 Downloader和一些工具类的实现

    1、我们来看看Downloader 类的定义。
    接下来我们就在download(String url)里写逻辑了

    public class Downloader {
        public void download(String url) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4

    2、首先我们要获取文件名和 文件的保存路径,然后和服务器建立链接
    得到链接对象 httpUrlConnection

    public void download(String url) {
     //获取文件名
            String httpFileName = HttpUtils.getHttpFileName(url);
            //文件的下载路径
            httpFileName = Constant.PATH+"\\"+httpFileName;
    
            //获取链接对象
            HttpURLConnection httpUrlConnection = null;
            try {
                httpUrlConnection = HttpUtils.getHttpUrlConnection(url);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这里我们用到了 HttpUtils工具类和Constant常量我们去看看怎么实现的吧
    HttpUtils

    public class HttpUtils {
    
        /**
         * 获取 HttpURLConnection 链接对象
         * @param url
         * @return
         * @throws IOException
         */
        public static HttpURLConnection getHttpUrlConnection(String url) throws IOException {
            URL httpUrl = new URL(url);
            //获取链接
            URLConnection urlConnection = httpUrl.openConnection();
            //向网站所在的服务器发送标识信息
            urlConnection.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.46");
    
            return (HttpURLConnection)urlConnection;
        }
    
        /**
         * 获取文件名
         * @param url
         * @return
         */
        public static String getHttpFileName(String url){
            int index = url.lastIndexOf("/");
            return url.substring(index+1);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这里指的注意了:urlConnection.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.46");这行代码是模仿浏览器对服务器发的请求。怎么知道这个的呢?
    1、先打开浏览器 按f12 打开开发者工具,找到网络的选项在这里插入图片描述
    然后打开一个网络请求就能看到很多内容了
    在这里插入图片描述

    Constant

    public class Constant {
        public static final  String PATH = "E:\\XBDownloader";
    }
    
    • 1
    • 2
    • 3

    3、我们有了要保存的位置,就因该看看有没有该文件夹,没有就应该创建

    		//判断文件甲是否存在
            // 创建File对象
            File path = new File(Constant.PATH);
            if(!path.exists()){
                //不存在就创建文件夹
                boolean mkdirs = path.mkdirs();
                if(!mkdirs){
                    System.out.println("创建失败");
                }
    
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4、根据链接对象httpUrlConnection 来下载文件到本地

    try(
                    //获得输入流
                    InputStream inputStream = httpUrlConnection.getInputStream();
                    //缓冲流
                    BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                    //输出流 把文件缓存到磁盘
                    FileOutputStream fileOutputStream = new FileOutputStream(httpFileName);
                    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            ){
                int len = -1;
                while ( (len =bufferedInputStream.read()) != -1){
                    bufferedOutputStream.write(len);
                }
    
    
            } catch (IOException e) {
                System.out.println("文件下载失败");
                throw new RuntimeException(e);
            } finally {
                //关闭链接对象
                httpUrlConnection.disconnect();
    
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    解释
    1、InputStream inputStream = httpUrlConnection.getInputStream();:这行代码通过调用httpUrlConnection对象的getInputStream()方法来获取输入流,该输入流包含了从网络URL下载的文件数据。
    2、BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);:这行代码创建了一个缓冲输入流对象bufferedInputStream,它包装了之前获取的输入流。缓冲输入流可以提高读取文件的性能,因为它可以减少实际的磁盘I/O操作次数
    3、FileOutputStream fileOutputStream = new FileOutputStream(httpFileName);:这行代码创建了一个文件输出流对象fileOutputStream,它将用于将文件数据写入到本地磁盘上的指定文件名(httpFileName)中。
    4、BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);:这行代码创建了一个缓冲输出流对象bufferedOutputStream,它包装了之前创建的文件输出流。缓冲输出流可以提高写入文件的性能,因为它可以减少实际的磁盘I/O操作次数。

    1.5 结果

    输入我上面提供的资源地址就能下载了
    在这里插入图片描述

    二、加上进度条

    在这里插入图片描述
    文件目录
    在这里插入图片描述

    新增加了DownloaderInfo

    2.1 DownloaderInfo实现

    根据进度条可知 这我们应该开的另外一个线程,让它不断的去读取文件大小来统计下载速度
    所以我们实现一个 Runnable 接口并重写 run() 方法

    public class DownloaderInfo implements Runnable{
    
        @Override
        public void run() {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    定义成员变量

    public class DownloaderInfo implements Runnable{
     //下载文件总大小
        private long httpFileContentLength;
    
        //本地已经下载的大小
        public Double finishedSize;
    
        //前一次下载大小
        public  double proDownSize;
    
        //本次下载大小
        public volatile double downSize;
    
        public DownloaderInfo() {}
        public DownloaderInfo(long httpFileContentLength) {
            this.httpFileContentLength = httpFileContentLength;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里需要注意 public volatile double downSize;这里加 volatile 关键字。让内存时刻保持最新值,因为这里有两个线程来操作的。
    第一个线程 是下载线程 每次都会更新当前值
    第二线程就是监控线程,每次读取它来监控下载进度


    重写 @Override run()方法

    @Override
        public void run() {
    
            //计算文件总大小 单位是M
            String downFileSize = String.format("%.2f",httpFileContentLength/ Constant.MB);
    
            //计算每秒下载速度KB
            int speed = (int)((downSize-proDownSize) / 1024d);
    
            proDownSize= downSize;
    
            //剩余文件大小
            double remainSize = httpFileContentLength - finishedSize - downSize;
    
            //计算剩余时间
            double rTime = remainSize / 1024 / speed;
            String remainTime = String.format("%.1f", rTime);
            if("Infinity".equals(remainTime)){
                remainTime = "————";
            }
            //已经下载大小
            String currentFileSize = String.format("%.2f", ((downSize - finishedSize) / Constant.MB));
            String downInfo = String.format("下载进度:%s mb / %s mb ==== 速度:%s kb/s ====剩余时间 %s 秒",
                    currentFileSize, downFileSize, speed, remainTime);
            System.out.print("\r");
            System.out.print(downInfo);
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2.2修改Downloader类

    既然我们需要时刻监控这个类的下载情况那么我们就需要创建线程
    这里我用的是ScheduledThreadPool 线程池 用于创建一个定时任务执行器

    添加成员变量 定时任务执行器

    private ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
    
    • 1

    然后在download()方法里添加监控线程

    //创建获取下载信息的对象
    downloaderInfo = new DownloaderInfo(contentLength);
    downloaderInfo.finishedSize=Double.longBitsToDouble(localFileLength) ;
    //监控线程
    scheduled.scheduleAtFixedRate(downloaderInfo,0,1, TimeUnit.SECONDS);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后不要忘记释放资源

     } finally {
                //关闭链接对象
                httpUrlConnection.disconnect();
                scheduled.shutdownNow();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3更新后的完整Downloader类

    package com.xbfinal.core;
    
    import com.xbfinal.utils.Constant;
    import com.xbfinal.utils.FileUtils;
    import com.xbfinal.utils.HttpUtils;
    
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.ScheduledThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @version 1.0
     * @Author 笑霸final
     * @Date 2023/10/18 18:46
     * @注释 下载器类
     */
    public class Downloader {
    
    //    private ScheduledExecutorService  scheduled = new ScheduledThreadPoolExecutor(1);
    //    private ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
        private ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
    
        public void download(String url) {
            //获取文件名
            String httpFileName = HttpUtils.getHttpFileName(url);
            //文件的下载路径
            httpFileName = Constant.PATH+"\\"+httpFileName;
    
            //获取本地文件大小
            long localFileLength = FileUtils.getFileSize(httpFileName);
    
            //获取链接对象
            HttpURLConnection httpUrlConnection = null;
            DownloaderInfo downloaderInfo = null;
            try {
                httpUrlConnection = HttpUtils.getHttpUrlConnection(url);
                //获取下载文件的总大小
                int contentLength = httpUrlConnection.getContentLength();
                //判断文件是否已经下载过?
                if(localFileLength >= contentLength){
                    try{
                        System.out.println("已经下载过无须在下载~~~");
                        return;
                    }finally {
                        //关闭链接对象
                        httpUrlConnection.disconnect();
                    }
                }
    
                //创建获取下载信息的对象
                downloaderInfo = new DownloaderInfo(contentLength);
                downloaderInfo.finishedSize=Double.longBitsToDouble(localFileLength) ;
                //监控线程
                scheduled.scheduleAtFixedRate(downloaderInfo,0,1, TimeUnit.SECONDS);
    
    
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
            //判断文件甲是否存在
            // 创建File对象
            File path = new File(Constant.PATH);
            if(!path.exists()){
                //不存在就创建文件夹
                boolean mkdirs = path.mkdirs();
                if(!mkdirs){
                    System.out.println("创建失败");
                }
    
            }
    
            try(
                    //获得输入流
                    InputStream inputStream = httpUrlConnection.getInputStream();
                    //缓冲流
                    BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                    //输出流 把文件缓存到磁盘
                    FileOutputStream fileOutputStream = new FileOutputStream(httpFileName);
                    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            ){
                int len = -1;
                byte[] bytes = new byte[Constant.BYTE_SIZE];
                while ( (len =bufferedInputStream.read(bytes)) != -1){
                    downloaderInfo.downSize+=len;
                    bufferedOutputStream.write(bytes,0,len);
    
                }
    
    
            } catch (IOException e) {
                System.out.println("文件下载失败");
                throw new RuntimeException(e);
            } finally {
                //关闭链接对象
                httpUrlConnection.disconnect();
                scheduled.shutdownNow();
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    三、多线程下载器

    3.1下载思路

    最开始 我们是一个线程负责下,如果我们多个线程下载是不是就需要把资源文件分块?然后不同的线程负责下不同的块!最后拼接起来对吧
    在这里插入图片描述
    在这里插入图片描述
    每次发送请求给服务器说 下载具体的那一块就行了http协议也支持

    目录
    在这里插入图片描述

    3.2 更新HttpUtils类

    我们添加新的方法

    /**
         * 分块下载的获取链接
         * @param url       下载地址
         * @param startPost 块的起始位置
         * @param startEnd  块的结束位置
         * @return
         */
        public static HttpURLConnection getHttpUrlConnection(String url,long startPost,long startEnd) throws IOException {
            HttpURLConnection httpUrlConnection = getHttpUrlConnection(url);
            System.out.println("下载的区间是"+startPost+"-"+startEnd );
            if(startEnd != 0){
                //第一块+中间数据块
                httpUrlConnection.setRequestProperty("RANGE","bytes="+startPost+"-"+startEnd);
            }else{
                //最后一块的数据
                httpUrlConnection.setRequestProperty("RANGE","bytes="+startPost+"-");
            }
            return httpUrlConnection;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    ("RANGE","bytes="+startPost+"-"+startEnd) 这里也是固定的

    /**
         * 获取文件名
         * @param url
         * @return
         */
        public static String getHttpFileName(String url){
            int index = url.lastIndexOf("/");
            return url.substring(index+1);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    /**
         *获取下载文件的大小
         * @param url
         * @return
         * @throws IOException
         */
        public static long getHttpFileLength(String url) throws IOException {
            HttpURLConnection httpUrlConnection= null;
            try{
                 httpUrlConnection = getHttpUrlConnection(url);
                 return httpUrlConnection.getContentLengthLong();
            }finally {
                assert httpUrlConnection != null;
                httpUrlConnection.disconnect();
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.3更新后的常量类Constant

    public class Constant {
        public static final  String PATH = "E:\\XBDownloader\\";
    
        public static final Double MB = 1024d * 1024d;
    
        public static final int BYTE_SIZE = 1024 * 100;
    
        public static final int THREAD_NOW = 5;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.4下载任务DownloaderTask的实现

    这个类就是我们以后多线的一个线程内,将来开很多线程都是实例化这个类,因为要合并分块,要等所有的分块下载完成后,才能合并,所以需要返回值.
    所以我们要实现Callable接口

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class DownloaderTask implements Callable<Boolean> {
    @Override
        public Boolean call() throws Exception {
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    给它添加成员变量

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class DownloaderTask implements Callable<Boolean> {
    	//url
     	private String url;
      	//块的起始地址
        private long startPost;
    	//块的结束地址
        private long startEnd;
    	//块的编号
        private int part;
    @Override
        public Boolean call() throws Exception {
        
    	}
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    接下来我吗就重写call方法

     @Override
        public Boolean call() throws Exception {
    
            //获取文件名
            String httpFileName = HttpUtils.getHttpFileName(url);
            //分块的文件名
            httpFileName +=".temp"+part;
            //下载路径
            httpFileName = Constant.PATH+httpFileName;
    
            //分块下载的链接
            HttpURLConnection httpUrlConnection = HttpUtils.getHttpUrlConnection(url, startPost, startEnd);
    
    
            try(
                    InputStream inputStream = httpUrlConnection.getInputStream();
                    BufferedInputStream bis = new BufferedInputStream(inputStream);
                    RandomAccessFile randomAccess = new RandomAccessFile(httpFileName,"rw")
            ){
    
                byte[] buffer = new byte[Constant.BYTE_SIZE];
                int len=-1;
                //循环读取数据
                while ((len =bis.read(buffer)) !=-1){
                    //1秒内下载数据之和  (原子类)
                    DownloaderInfo.downSize.add(len);
                    randomAccess.write(buffer,0,len);
                }
    
            }catch (FileNotFoundException e){
                System.out.println("文件未找到");
                return false;
            }catch (Exception e){
                System.out.println("未知问题");
                return false;
            }finally {
            //释放链接
                httpUrlConnection.disconnect();
            }
            return true;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    3.5更新Downloader

    添线程池成员变量(负责多线程下载)

     //任务线程池对象
      private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
        Constant.THREAD_NOW, // 核心线程数
        Constant.THREAD_NOW, // 最大线程数
        0, TimeUnit.SECONDS, // 空闲线程存活时间,这里设置为0,表示线程不会自动终止
        new ArrayBlockingQueue<>(Constant.THREAD_NOW) // 任务队列,用于存储待执行的任务
    );
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    添加文件切分的方法

     /**
         * 文件切分
         * @param url
         * @param futureList
         */
        public void split(String url , ArrayList<Future> futureList){
    
            try {
                //获取下载文件大小
                long httpFileLength = HttpUtils.getHttpFileLength(url);
                //计算切分后文件的大小
                long size= httpFileLength/Constant.THREAD_NOW;
                //计算分块个数
                for (int i = 0; i < Constant.THREAD_NOW; i++) {
                    //下载起始位置
                    long startPost = i*size;
                    //计算结束位置
                    long startEnd;
                    if(i==Constant.THREAD_NOW-1){
                        startEnd=0;
                    }else{
                        startEnd=startPost+size;
                    }
                    if(startPost!=0){
                        startPost++;
                    }
                    //创建任务
                    DownloaderTask downloaderTask = new DownloaderTask(url, startPost, startEnd,i);
                    //将任务提交到线程池中
                    Future<Boolean> submit = poolExecutor.submit(downloaderTask);
                    futureList.add(submit);
                }
    
    
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    合并文件的方法

    /**
         * 合并文件的方法
         * @param fileName
         * @return
         */
        public boolean merge(String fileName){
            System.out.print("\r");
            System.out.println("开始合并文件");
            byte[] buffer = new byte[Constant.BYTE_SIZE];
            int len = -1;
            try( RandomAccessFile accessFile = new RandomAccessFile(fileName,"rw")){
                for (int i = 0; i < Constant.THREAD_NOW; i++) {
                    try( FileInputStream fileInputStream = new FileInputStream(fileName+".temp"+i);
                         BufferedInputStream bis = new BufferedInputStream(fileInputStream)
                    ){
                        while ((len = bis.read(buffer)) != -1){
                            accessFile.write(buffer,0,len);
                        }
                    }
                }
                System.out.print("\r");
                System.out.println("文件合并完毕");
            } catch (FileNotFoundException e) {
                System.out.println("文件未找到");
                return false;
            } catch (IOException e) {
                System.out.println("未知错误");
                return false;
            }
            return true;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    最后清空临时文件

      /**
         * 清空临时文件
         * @param fileName
         * @return
         */
        public boolean clearTemp(String fileName){
            for (int i = 0; i < Constant.THREAD_NOW; i++) {
                File file = new File(fileName + ".temp" + i);
                file.delete();
            }
            return true;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.6Downloader中的download()方法

    public void download(String url) {
            //获取文件名
            String httpFileName = HttpUtils.getHttpFileName(url);
            //文件的下载路径
            httpFileName = Constant.PATH+httpFileName;
    
            //获取本地文件大小
            long localFileLength = FileUtils.getFileSize(httpFileName);
    
            //获取链接对象
            HttpURLConnection httpUrlConnection = null;
            DownloaderInfo downloaderInfo = null;
            try {
                httpUrlConnection = HttpUtils.getHttpUrlConnection(url);
                //获取下载文件的总大小
                int contentLength = httpUrlConnection.getContentLength();
                //判断文件是否已经下载过?
                if(localFileLength >= contentLength){
                    try{
                        System.out.println("已经下载过无须在下载~~~");
                        return;
                    }finally {
                        //关闭链接对象
                        httpUrlConnection.disconnect();
                    }
                }
                //创建获取下载信息的对象
                downloaderInfo = new DownloaderInfo(contentLength);
    //            downloaderInfo.finishedSize=Double.longBitsToDouble(localFileLength) ;
                //监控线程 每1秒执行一次
                scheduled.scheduleAtFixedRate(downloaderInfo,1,1, TimeUnit.SECONDS);
                //切分任务
                ArrayList<Future> list = new ArrayList<>();
                split(url,list);
                //获取集合中的数据
                list.forEach(future -> {
                    try {
                        future.get();//方便后续合并文件 所有文件下载未完成会阻塞
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } catch (ExecutionException e) {
                        throw new RuntimeException(e);
                    }
                } );
                //合并文件
                merge(httpFileName);
                //删除临时文件
                clearTemp(httpFileName);
    
    
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                //关闭链接对象
                httpUrlConnection.disconnect();
                scheduled.shutdownNow();
                poolExecutor.shutdownNow();
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    最后运行的结果
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    Java/JDK 21正式发布!15个特性一览
    完成了一个小项目:修改了一个用PHP+MySQL写的建网站用的CMS原程序
    JAVA毕业设计航空订票系统计算机源码+lw文档+系统+调试部署+数据库
    EANet:用于医学图像分割的迭代边缘注意力网络
    Weblogic 反序列化命令执行漏洞(CVE-2018-2628)漏洞复现【vulhub靶场】
    动态规划(算法竞赛、蓝桥杯)--概率DP求概率
    LeetCode-145. Binary Tree Postorder Traversal [C++][Java]
    OCP Java17 SE Developers 复习题15(完)
    [算法1-排序](.NET源码学习)& LINQ & Lambda
    基于Linux上MySQL8.*版本的安装-参考官网
  • 原文地址:https://blog.csdn.net/weixin_52062043/article/details/133960507