Write a Downloader with JAVA Episode 2


1. Development environment and tools

Development environment and tools
IDEA
JDK8
UTF-8

2. Overview of package names

constant: the package for storing constant classes
core: the package for storing the core classes of the downloader
util: the package for storing tool classes
Main: the main class

3. Project structure

insert image description here
Main is the main class in which the main function resides.

4. Use steps

1. Write code

Constant.java:

package com.downloader.constant;

/*常量类*/
public class Constant {
    
    
    public static final String PATH="D:\\Java_dm\\TestDownloaderPath\\";


    public static final double MB=1024d*1024d;


    public static final int BYTE_SIZE=1024*100;


    public static final int THREAD_NUM=5;
}

Downloader.java

package com.downloader.core;

import com.downloader.constant.Constant;
import com.downloader.util.FileUtils;
import com.downloader.util.HttpUtils;
import com.downloader.util.LogUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.concurrent.*;

/*下载器*/
public class Downloader {
    
    

    public ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(1);
    public ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(Constant.THREAD_NUM,Constant.THREAD_NUM,0,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
    private CountDownLatch countDownLatch=new CountDownLatch(Constant.THREAD_NUM);
    public void download(String url){
    
    
        //获取文件名
        String httpFileName = HttpUtils.getHttpFileName(url);
        //文件保存路径
        httpFileName= Constant.PATH+httpFileName;

        /*获取本地文件的大小*/
        long localFileLength = FileUtils.getFileContentLength(httpFileName);

        //获取连接对象
        HttpURLConnection httpURLConnection =null;
        DownloadInfoThread downloadInfoThread=null;
        try {
    
    
            httpURLConnection = HttpUtils.getHttpURLConnection(url);
            int contentLength = httpURLConnection.getContentLength();

            /*判断文件是否已经下载过*/
            if (localFileLength>=contentLength){
    
    
                LogUtils.info("{}已经下载完毕,无需重新下载",httpFileName);
                return;
            }

            /*创建获取下载信息的任务对象*/
            downloadInfoThread = new DownloadInfoThread(contentLength);
            scheduledExecutorService.scheduleAtFixedRate(downloadInfoThread,1,1, TimeUnit.SECONDS);

            //切分任务
            ArrayList<Future> list = new ArrayList<>();
            spilt(url,list);

            countDownLatch.await();//等待所有线程结束

            /*合并文件*/
           if(merge(httpFileName)){
    
    
               clearTemp(httpFileName);//清楚文件
           }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            System.out.print("\r");
            System.out.print("下载完成");
            if (httpURLConnection!=null){
    
    
                httpURLConnection.disconnect();//关闭连接
            }

            scheduledExecutorService.shutdownNow();//关闭线程
            poolExecutor.shutdown();//关闭线程池
        }

    }


    public void spilt(String url, ArrayList<Future> futureList){
    
    
        try {
    
    
            long contentLength = HttpUtils.getHttpFileContentLength(url);
            long size = contentLength / Constant.THREAD_NUM;
            for (int i = 0; i < Constant.THREAD_NUM; i++) {
    
    
                long startPos=i*size;
                long endPos;
                if(i==Constant.THREAD_NUM-1){
    
    
                    endPos=0;
                }else {
    
    
                    endPos=startPos+size-1;
                }
                DownloaderTask downloaderTask = new DownloaderTask(url, startPos, endPos,i,countDownLatch);
                /*将任务提交到线程池中*/
                Future<Boolean> future = poolExecutor.submit(downloaderTask);
                futureList.add(future);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public boolean merge(String fileName){
    
    
        System.out.print("\n");
        LogUtils.info("开始合并文件{}",fileName);
        byte[] buffer=new byte[Constant.BYTE_SIZE];
        int len=-1;
        try(RandomAccessFile accessFile=new RandomAccessFile(fileName,"rw")){
    
    
            for (int i = 0; i < Constant.THREAD_NUM; i++) {
    
    
                try(BufferedInputStream bis=new BufferedInputStream(new FileInputStream(fileName+".temp"+i))){
    
    
                    while ((len=bis.read(buffer))!=-1){
    
    
                        accessFile.write(buffer,0,len);
                    }
                }
            }
            LogUtils.info("文件合并完毕{}",fileName);
        }catch (Exception e){
    
    
            e.printStackTrace();
            return false;
        }
        return true;
    }


    public boolean clearTemp(String fileName){
    
    
        for (int i = 0; i < Constant.THREAD_NUM; i++) {
    
    
            File file=new File(fileName+".temp"+i);
            file.delete();
        }
        return true;
    }




}

DownloaderTask.java

package com.downloader.core;

import com.downloader.constant.Constant;
import com.downloader.util.HttpUtils;
import com.downloader.util.LogUtils;

import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;

public class DownloaderTask implements Callable<Boolean> {
    
    
    /*分块下载任务*/
    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 httpFileName = HttpUtils.getHttpFileName(url);
        httpFileName=httpFileName+".temp"+part;//分块的文件名
        httpFileName= Constant.PATH+httpFileName;//下载路径

        /*获取分块下载的连接*/
        HttpURLConnection httpURLConnection = HttpUtils.getHttpURLConnection(url, startPos, endPos);

        try(
                InputStream inputStream=httpURLConnection.getInputStream();
                BufferedInputStream bis=new BufferedInputStream(inputStream);
                RandomAccessFile accessFile= new RandomAccessFile(httpFileName,"rw");
        ){
    
    
            byte[] buffer = new byte[Constant.BYTE_SIZE];
            int len=-1;
            while ((len=bis.read(buffer))!=-1){
    
    
                DownloadInfoThread.downSize.add(len);
                accessFile.write(buffer,0,len);
            }
        }catch (FileNotFoundException e){
    
    
            LogUtils.error("下载文件不存在{}",url);
            return false;
        }catch (Exception e){
    
    
            LogUtils.error("下载出现异常");
            return false;
        }finally {
    
    
            httpURLConnection.disconnect();
            countDownLatch.countDown();//减1操作,等待清零
        }
        return true;
    }
}

DownloadInfoThread.java

package com.downloader.core;

import com.downloader.constant.Constant;

import java.util.concurrent.atomic.LongAdder;

public class DownloadInfoThread implements Runnable{
    
    

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

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

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

    /*前一次下载的大小*/
    public double prevSize;

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

    @Override
    public void run() {
    
    
        /*计算文件总大小   单位:MB*/
        String httpFileSize = String.format("%.2f", httpFileContentLength / Constant.MB);
        /*计算每秒下载速度  kb*/
        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 ("Infinity".equalsIgnoreCase(remainTime)){
    
    
            remainTime="-";
        }

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

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

        System.out.print("\r");
        System.out.print(downInfo);

    }
}

FileUtils.java

package com.downloader.util;

import java.io.File;

public class FileUtils {
    
    

    /*获取本地文件的大小*/
    public static long getFileContentLength(String path){
    
    
        File file = new File(path);
        return file.exists()&&file.isFile()?file.length():0;
    }

}

HttpUtils.java

package com.downloader.util;

import java.io.IOException;
import java.net.*;

/*http相关工具类*/
public class HttpUtils {
    
    

    public static long getHttpFileContentLength(String url) throws IOException {
    
    
        int contentLength;
        HttpURLConnection httpURLConnection=null;
        try {
    
    
            httpURLConnection = getHttpURLConnection(url);
            contentLength = httpURLConnection.getContentLength();
        } finally {
    
    
            if(httpURLConnection!=null){
    
    
                httpURLConnection.disconnect();
            }
        }
        return contentLength;
    }

    /*分块下载方法*/
    public static HttpURLConnection getHttpURLConnection(String url,long startPos,long endPos) throws IOException {
    
    
        HttpURLConnection httpURLConnection=getHttpURLConnection(url);
        LogUtils.info("下载的区间是:{}--{}",startPos,endPos);
        if(endPos!=0){
    
    
            httpURLConnection.setRequestProperty("RANGE","bytes="+startPos+"-"+endPos);
        }else {
    
    
            httpURLConnection.setRequestProperty("RANGE","bytes="+startPos+"-");
        }
        return httpURLConnection;
    }



    /*获取HttpURLConnection连接对象*/
    public static HttpURLConnection getHttpURLConnection(String url) throws IOException {
    
    
        URL httpUrl = new URL(url);
        HttpURLConnection urlConnection = (HttpURLConnection)httpUrl.openConnection();
        //向文件所在的服务器发送标识信息
        urlConnection.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (HTML, like Gecko) Chrome/14.0.835.163 Safari/535.1");
        return urlConnection;
    }

    /*获取下载文件的名称*/
    public static String getHttpFileName(String url){
    
    
        int indexOf = url.lastIndexOf("/");
        return url.substring(indexOf+1);
    }
}

LogUtils.java

package com.downloader.util;

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

/*日志工具类*/
public class LogUtils {
    
    

     public static void info(String msg,Object... args){
    
    
          print(msg,"-info-",args);
     }

     public static void error(String msg,Object... args){
    
    
          print(msg,"-error-",args);
     }

     private static void print(String msg,String level,Object... args){
    
    
          if(args!=null&&args.length>0){
    
    
               msg=String.format(msg.replace("{}","%s"),args);
          }
          String name = Thread.currentThread().getName();
          System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss"))+""+name+level+msg);

     }
}

Main.java

package com.downloader;

import com.downloader.core.Downloader;
import com.downloader.util.LogUtils;

import java.util.Scanner;

public class Main {
    
    
    /*主类,程序的入口*/
    public static void main(String[] args) {
    
    
        String url=null;//用来放下载连接的地址。
        if(args!=null && args.length!=0){
    
    //判断主函数的传入字符数组是否为空
            url=args[0];//如果不为空,对url进行赋值arg[0]
        }else {
    
    
            while (true){
    
    //主函数传入字符数组为空,进入while循环
//                System.out.println("请输入下载文件的地址。");
                LogUtils.info("请输入下载文件的地址");
                Scanner scanner=new Scanner(System.in);//在控制台中获取输入的信息。
                url=scanner.next();//对url进行赋值,为控制台中输入的信息。
                if(url!=null){
    
    //如果url不为空,结束while循环。
                    break;
                }
            }
        }
        Downloader downloader = new Downloader();
        downloader.download(url);
    }
}

2. Run the program

insert image description here

Link address:
https://dldir1.qq.com/qqfile/qq/PCQQ9.7.1/QQ9.7.1.28940.exe
insert image description here


Summarize

This is a program that downloads a file with multithreading, mainly the understanding of multithreading.

Guess you like

Origin blog.csdn.net/weixin_45345143/article/details/131374718