用JAVA写一个下载器第2集


一、开发环境及工具

开发环境及工具
IDEA
JDK8
UTF-8

二、包名概览

constant:存放常量类的包
core:存放了下载器核心类的包
util:存放工具类的包
Main:主类

三、项目结构

在这里插入图片描述
Main是主类,主函数在其中。

四、使用步骤

1.编写代码

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.运行程序

在这里插入图片描述

链接地址:
https://dldir1.qq.com/qqfile/qq/PCQQ9.7.1/QQ9.7.1.28940.exe
在这里插入图片描述


总结

这是一个用多线程下载一个文件的程序,主要是对多线程的理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值