zoukankan      html  css  js  c++  java
  • [工具库]JFileDownloader工具类——多线程下载网络文件,并保存在本地

    本人大四即将毕业的准程序员(JavaSE、JavaEE、android等)一枚,小项目也做过一点,于是乎一时兴起就写了一些工具。

    我会在本博客中陆续发布一些平时可能会用到的工具。

    代码质量可能不是很好,大家多担待!

    代码或者思路有不妥之处,还希望大牛们能不吝赐教哈!

    以下代码为本人原创,转载请注明:

    本文转载,来自:http://www.cnblogs.com/tiantianbyconan/archive/2013/02/20/2919132.html

    JFileDownloader:用于多线程下载网络文件,并保存在本地。

    源码如下:

    1.JFileDownloader类:主要负责下载的初始化可启动工作。

    View Code
      1 package com.wangjie.extrautil.jfiledownloader;
      2 
      3 import java.io.File;
      4 import java.net.HttpURLConnection;
      5 import java.net.URL;
      6 
      7 /**
      8  * 
      9  * @author wangjie
     10  * @version 创建时间:2013-2-7 下午1:40:52
     11  */
     12 public class JFileDownloader{
     13     private String urlPath;
     14     private String destFilePath;
     15     private int threadCount;
     16     private JFileDownloadThread[] threads;
     17     
     18     private JFileDownloadListener fileDownloadListener; // 进度监听器
     19     private JFileDownloaderNotificationThread notificationThread; // 通知进度线程
     20     
     21     private File destFile;
     22     /**
     23      * 下载过程中文件的后缀名。
     24      */
     25     public final static String DOWNLOADING_SUFFIX = ".jd"; 
     26     /**
     27      * 默认使用的线程数量。<br>
     28      * 如果不设置线程数量参数(threadCount),则默认线程启动数量为1,即单线程下载。
     29      */
     30     public static final int DEFAULT_THREADCOUNT = 1;
     31     /**
     32      * 生成JFileDownloader对象。
     33      * @param urlPath 要下载的目标文件URL路径
     34      * @param destFilePath 要保存的文件目标(路径+文件名)
     35      * @param threadCount 下载该文件所需要的线程数量
     36      */
     37     public JFileDownloader(String urlPath, String destFilePath, int threadCount) {
     38         this.urlPath = urlPath;
     39         this.destFilePath = destFilePath;
     40         this.threadCount = threadCount;
     41     }
     42     /**
     43      * 生成JFileDownloader对象,其中下载线程数量默认是1,也就是选择单线程下载。
     44      * @param urlPath urlPath 要下载的目标文件URL路径
     45      * @param destFilePath destFilePath 要保存的文件目标(路径+文件名)
     46      */
     47     public JFileDownloader(String urlPath, String destFilePath) {
     48         this(urlPath, destFilePath, DEFAULT_THREADCOUNT);
     49     }
     50     /**
     51      * 默认的构造方法,使用构造方法后必须要调用set方法来设置url等下载所需配置。
     52      */
     53     public JFileDownloader() {
     54         
     55     }
     56     /**
     57      * 开始下载方法(流程分为3步)。
     58      * <br><ul>
     59      * <li>检验URL的合法性<br>
     60      * <li>计算下载所需的线程数量和每个线程需下载多少大小的文件<br>
     61      * <li>启动各线程。
     62      * </ul>
     63      * @author wangjie
     64      * @throws Exception 如果设置的URL,includes等参数不合法,则抛出该异常
     65      */
     66     public void startDownload() throws Exception{
     67         checkSettingfValidity(); // 检验参数合法性
     68         
     69         URL url = new URL(urlPath);
     70         HttpURLConnection conn = (HttpURLConnection)url.openConnection();
     71         conn.setConnectTimeout(20 * 1000);
     72         // 获取文件长度
     73         long size = conn.getContentLength();
     74 //        int size = conn.getInputStream().available();
     75         if(size < 0 || null == conn.getInputStream()){
     76             throw new Exception("网络连接错误,请检查URL地址是否正确");
     77         }
     78         conn.disconnect();
     79         
     80         
     81         // 计算每个线程需要下载多少byte的文件
     82         long perSize = size % threadCount == 0 ? size / threadCount : (size / threadCount + 1);
     83         // 建立目标文件(文件以.jd结尾)
     84         destFile = new File(destFilePath + DOWNLOADING_SUFFIX);
     85         destFile.createNewFile();
     86         
     87         threads = new JFileDownloadThread[threadCount];
     88         
     89         // 启动进度通知线程
     90         notificationThread = new JFileDownloaderNotificationThread(threads, fileDownloadListener, destFile, size);
     91         notificationThread.start();
     92         
     93         // 初始化若干个下载线程
     94         for(int i = 0; i < threadCount; i++){
     95             if(i != (threadCount - 1)){
     96                 threads[i] = new JFileDownloadThread(urlPath, destFile, 
     97                         i * perSize, perSize, notificationThread);
     98             }else{
     99                 threads[i] = new JFileDownloadThread(urlPath, destFile, 
    100                         i * perSize, size - (threadCount - 1) * perSize, notificationThread);
    101             }
    102             threads[i].setPriority(8);
    103 //            threads[i].start();
    104         }
    105         // 启动若干个下载线程(因为下载线程JFileDownloaderNotificationThread中使用了threads属性,所以必须等下载线程全部初始化以后才能启动线程)
    106         for(JFileDownloadThread thread : threads){
    107             thread.start();
    108         }
    109         
    110     }
    111     /**
    112      * 取消所有下载线程。
    113      * @author wangjie
    114      */
    115     public void cancelDownload(){
    116         if(null != threads && 0 != threads.length && null != notificationThread){
    117             for(JFileDownloadThread thread : threads){ // 终止所有下载线程
    118                 thread.cancelThread();
    119             }
    120             notificationThread.cancelThread(); // 终止通知线程
    121             System.out.println("下载已被终止。");
    122             return;
    123         }
    124         System.out.println("下载线程还未启动,无法终止。");
    125     }
    126     
    127     /**
    128      * 设置要下载的目标文件URL路径。
    129      * @author wangjie
    130      * @param urlPath 要下载的目标文件URL路径
    131      * @return 返回当前JFileDownloader对象
    132      */
    133     public JFileDownloader setUrlPath(String urlPath) {
    134         this.urlPath = urlPath;
    135         return this;
    136     }
    137     /**
    138      * 设置要保存的目标文件(路径+文件名)。
    139      * @author wangjie
    140      * @param destFilePath 要保存的文件目标(路径+文件名)
    141      * @return 返回当前JFileDownloader对象
    142      */
    143     public JFileDownloader setDestFilePath(String destFilePath) {
    144         this.destFilePath = destFilePath;
    145         return this;
    146     }
    147     /**
    148      * 设置下载该文件所需要的线程数量。
    149      * @author wangjie
    150      * @param threadCount 下载该文件所需要的线程数量
    151      * @return 返回当前JFileDownloader对象
    152      */
    153     public JFileDownloader setThreadCount(int threadCount) {
    154         this.threadCount = threadCount;
    155         return this;
    156     }
    157     
    158     //观察者模式来获取下载进度
    159     /**
    160      * 设置监听器,以获取下载进度。
    161      */
    162     public JFileDownloader setFileDownloadListener(
    163             JFileDownloadListener fileDownloadListener) {
    164         this.fileDownloadListener = fileDownloadListener;
    165         return this;
    166     }
    167     /**
    168      * 通过该方法移出相应的监听器对象。
    169      * @author wangjie
    170      * @param fileDownloadListener 要移除的监听器对象
    171      */
    172     public void removeFileDownloadListener(
    173             JFileDownloadListener fileDownloadListener) {
    174         fileDownloadListener = null;
    175     }
    176 
    177 
    178     /**
    179      * 检验设置的参数是否合法。
    180      * @author wangjie
    181      * @throws Exception 目标文件URL路径不合法,或者线程数小于1,则抛出该异常
    182      */
    183     private void checkSettingfValidity() throws Exception{
    184         if(null == urlPath || "".equals(urlPath)){
    185             throw new Exception("目标文件URL路径不能为空");
    186         }
    187         if(threadCount < 1){
    188             throw new Exception("线程数不能小于1");
    189         }
    190     }
    191     
    192 }

    2.JFileDownloadListener接口:该接口用于监听JFileDownloader下载的进度。

    View Code
     1 package com.wangjie.extrautil.jfiledownloader;
     2 
     3 import java.io.File;
     4 
     5 /**
     6  * 
     7  * 该接口用于监听JFileDownloader下载的进度。
     8  * 
     9  * @author wangjie
    10  * @version 创建时间:2013-2-7 下午2:12:45
    11  */
    12 public interface JFileDownloadListener {
    13     /**
    14      * 该方法可获得文件的下载进度信息。
    15      * @author wangjie
    16      * @param progress 文件下载的进度值,范围(0-100)。0表示文件还未开始下载;100则表示文件下载完成。
    17      * @param speed 此时下载瞬时速度(单位:kb/每秒)。
    18      * @param remainTime 此时剩余下载所需时间(单位为毫秒)。
    19      */
    20     public void downloadProgress(int progress, double speed, long remainTime);
    21     /**
    22      * 文件下载完成会调用该方法。
    23      * @author wangjie
    24      * @param file 返回下载完成的File对象。
    25      * @param downloadTime 下载所用的总时间(单位为毫秒)。
    26      */
    27     public void downloadCompleted(File file, long downloadTime);
    28 }

    3.JFileDownloaderNotificationThread类:该线程为通知下载进度的线程。

    View Code
      1 package com.wangjie.extrautil.jfiledownloader;
      2 
      3 import java.io.File;
      4 import java.math.BigDecimal;
      5 
      6 /**
      7  * 该线程为通知下载进度的线程。
      8  * 用于在下载未完成时通知用户下载的进度,范围(0-100)。0表示文件还未开始下载;100则表示文件下载完成。
      9  * 此时下载瞬时速度(单位:kb/每秒)。
     10  * 在完成时返回下载完成的File对象给用户。返回下载所用的总时间(单位为毫秒)给用户。
     11  * @author wangjie
     12  * @version 创建时间:2013-2-17 下午12:23:59
     13  */
     14 public class JFileDownloaderNotificationThread extends Thread{
     15     private JFileDownloadThread[] threads;
     16     private JFileDownloadListener fileDownloadListener;
     17     private File destFile;
     18     private long destFileSize;
     19     private boolean isRunning; // 线程运行停止标志
     20     private boolean notificationTag; // 通知标志
     21     /**
     22      * 通过该方法构建一个进度通知线程。
     23      * @param threads 下载某文件需要的所有线程。
     24      * @param fileDownloadListener 要通知进度的监听器对象。
     25      * @param destFile 下载的文件对象。
     26      */
     27     public JFileDownloaderNotificationThread(JFileDownloadThread[] threads,
     28             JFileDownloadListener fileDownloadListener, File destFile, long destFileSize) {
     29         this.threads = threads;
     30         this.fileDownloadListener = fileDownloadListener;
     31         this.destFile = destFile;
     32         this.destFileSize = destFileSize;
     33     }
     34 
     35     /**
     36      * 不断地循环来就检查更新进度。
     37      */
     38     @Override
     39     public void run() {
     40         isRunning = true;
     41         long startTime = 0;
     42         if(null != fileDownloadListener){
     43             startTime = System.currentTimeMillis(); // 文件下载开始时间
     44         }
     45         
     46         long oldTemp = 0; // 上次已下载数据长度
     47         long oldTime = 0; // 上次下载的当前时间
     48         
     49         while(isRunning){
     50             if(notificationTag){ // 如果此时正等待检查更新进度。
     51                 // 计算此时的所有线程下载长度的总和
     52                 long temp = 0;
     53                 for(JFileDownloadThread thread : threads){
     54                     temp += thread.currentLength;
     55                 }
     56 //                System.out.println("temp: " + temp);
     57 //                System.out.println("destFileSize: " + destFileSize);
     58                 // 换算成进度
     59                 int progress = (int) ((double)temp * 100 / (double)destFileSize);
     60                 
     61                 // 把进度通知给监听器
     62                 if(null != fileDownloadListener){
     63                     // 计算瞬时速度
     64                     long detaTemp = temp - oldTemp; // 两次更新进度的时间段内的已下载数据差
     65                     long detaTime = System.currentTimeMillis() - oldTime; // 两次更新进度的时间段内的时间差 
     66                     // 两次更新进度的时间段内的速度作为瞬时速度
     67                     double speed = ((double)detaTemp / 1024) / ((double)(detaTime) / 1000); 
     68                     
     69                     // 保留小数点后2位,最后一位四舍五入
     70                     speed = new BigDecimal(speed).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
     71                     
     72                     // 计算剩余下载时间
     73                     double remainTime = (double)(destFileSize - temp) / speed;
     74                     if(Double.isInfinite(remainTime) || Double.isNaN(remainTime)){
     75                         remainTime = 0;
     76                     }else{
     77                         remainTime = new BigDecimal(remainTime).setScale(0, BigDecimal.ROUND_HALF_UP).longValue();
     78                     }
     79                     
     80                     // 通知监听者进度和速度以及下载剩余时间
     81                     fileDownloadListener.downloadProgress(progress, speed, (long)remainTime);
     82                     
     83                     // 重置上次已下载数据长度和上次下载的当前时间
     84                     oldTemp = temp;
     85                     oldTime = System.currentTimeMillis();
     86                 }
     87                 
     88                 // 如果下载进度达到100,则表示下载完毕
     89                 if(100 <= progress){
     90                     // 给下载好的文件进行重命名,即去掉DOWNLOADING_SUFFIX后缀
     91                     String oldPath = destFile.getPath();
     92                     File newFile = new File(oldPath.substring(0, oldPath.lastIndexOf(".")));
     93                     // 检查去掉后的文件是否存在。如果存在,则删除原来的文件并重命名下载的文件(即:覆盖原文件)
     94                     if(newFile.exists()){
     95                         newFile.delete();
     96                     }
     97                     System.out.println(destFile.renameTo(newFile));// 重命名
     98                     // 通知监听器,并传入新的文件对象
     99                     if(null != fileDownloadListener){
    100                         fileDownloadListener.downloadCompleted(newFile, System.currentTimeMillis() - startTime);
    101                     }
    102                     isRunning = false; // 文件下载完就结束通知线程。
    103                 }
    104                 notificationTag = false;
    105             }
    106             // 设置为每100毫秒进行检查并更新通知
    107             try {
    108                 Thread.sleep(100);
    109             } catch (InterruptedException e) {
    110                 e.printStackTrace();
    111             }
    112             
    113         }
    114         
    115     }
    116     /**
    117      * 调用这个方法,则会使得线程处于待检查更新进度状态。
    118      * @author wangjie
    119      */
    120     public synchronized void notificationProgress(){
    121         notificationTag = true;
    122     }
    123     /**
    124      * 取消该通知线程
    125      * @author wangjie
    126      */
    127     public void cancelThread(){
    128         isRunning = false;
    129     }
    130     
    131 
    132 }

    4.JFileDownloadThread类:真正的下载线程,该线程用于执行该线程所要负责下载的数据。

    View Code
      1 package com.wangjie.extrautil.jfiledownloader;
      2 
      3 import java.io.File;
      4 import java.io.IOException;
      5 import java.io.InputStream;
      6 import java.io.RandomAccessFile;
      7 import java.net.HttpURLConnection;
      8 import java.net.URL;
      9 
     10 /**
     11  * 
     12  * 真正的下载线程,该线程用于执行该线程所要负责下载的数据。
     13  * 
     14  * @author wangjie
     15  * @version 创建时间:2013-2-7 上午11:58:24
     16  */
     17 public class JFileDownloadThread extends Thread{
     18     private String urlPath;
     19     private File destFile;
     20     private long startPos;
     21     /**
     22      * 此线程需要下载的数据长度。
     23      */
     24     public long length;
     25     /**
     26      * 此线程现在已下载好了的数据长度。
     27      */
     28     public long currentLength;
     29     
     30     private JFileDownloaderNotificationThread notificationThread;
     31     private boolean isRunning = true;
     32     
     33     /**
     34      * 构造方法,可生成配置完整的JFileDownloadThread对象
     35      * @param urlPath 要下载的目标文件URL
     36      * @param destFile 要保存的目标文件
     37      * @param startPos 该线程需要下载目标文件第几个byte之后的数据
     38      * @param length 该线程需要下载多少长度的数据
     39      * @param notificationThread 通知进度线程
     40      */
     41     public JFileDownloadThread(String urlPath, File destFile, long startPos,
     42             long length, JFileDownloaderNotificationThread notificationThread) {
     43         this.urlPath = urlPath;
     44         this.destFile = destFile;
     45         this.startPos = startPos;
     46         this.length = length;
     47         this.notificationThread = notificationThread;
     48     }
     49     /**
     50      * 该方法将执行下载功能,并把数据存储在目标文件中的相应位置。
     51      */
     52     @Override
     53     public void run() {
     54         RandomAccessFile raf = null;
     55         HttpURLConnection conn = null;
     56         InputStream is = null;
     57         try {
     58 //            URL url = new URL("http://localhost:8080/firstserver/files/hibernate.zip");
     59             URL url = new URL(urlPath);
     60             conn = (HttpURLConnection)url.openConnection();
     61             conn.setConnectTimeout(20 * 1000);
     62             is = conn.getInputStream();
     63             raf = new RandomAccessFile(destFile, "rw");
     64             raf.setLength(conn.getContentLength()); // 设置保存文件的大小
     65 //            raf.setLength(conn.getInputStream().available());
     66             
     67             // 设置读入和写入的文件位置
     68             is.skip(startPos);
     69             raf.seek(startPos);
     70             
     71             currentLength = 0; // 当前已下载好的文件长度
     72             byte[] buffer = new byte[1024 * 1024];
     73             int len = 0;
     74             while(currentLength < length && -1 != (len = is.read(buffer))){
     75                 if(!isRunning){
     76                     break;
     77                 }
     78                 if(currentLength + len > length){
     79                     raf.write(buffer, 0, (int)(length - currentLength));
     80                     currentLength = length;
     81                     notificationThread.notificationProgress(); // 通知进度线程来更新进度
     82                     return;
     83                 }else{
     84                     raf.write(buffer, 0, len);
     85                     currentLength += len;
     86                     notificationThread.notificationProgress(); // 通知进度线程来更新进度
     87                 }
     88             }
     89         } catch (Exception e) {
     90             e.printStackTrace();
     91         } finally{
     92             try {
     93                 is.close();
     94                 raf.close();
     95                 conn.disconnect();
     96             } catch (IOException e) {
     97                 e.printStackTrace();
     98             }
     99         }
    100         
    101     }
    102     /**
    103      * 取消该线程下载
    104      * @author wangjie
    105      */
    106     public void cancelThread(){
    107         isRunning = false;
    108     }
    109     
    110     
    111 }

    使用方法如下:

     1 String urlPath = "http://localhost:8080/firstserver/files/test.zip";
     2 String destFilePath = "C:\\Users\\admin\\Desktop\\杂\\临时仓库\\test.zip";
     3 int threadCount = 3;
     4 
     5 JFileDownloader downloader = new JFileDownloader(urlPath, destFilePath, threadCount);
     6 //或者:
     7 JFileDownloader downloader = new JFileDownloader()
     8             .setUrlPath(urlPath)
     9             .setDestFilePath(destFilePath)
    10             .setThreadCount(threadCount)
    11             .setFileDownloadListener(new JFileDownloadListener() { // 设置进度监听器
    12                     public void downloadProgress(int progress, double speed, long remainTime) {
    13                         System.out.println("文件已下载:" + progress + "%,下载速度为:" + speed + "kb/s,剩余所需时间:" + remainTime + "毫秒");
    14                     }
    15                     public void downloadCompleted(File file, long downloadTime) {
    16                         System.out.println("文件:" + file.getName() + "下载完成,用时:" + downloadTime + "毫秒");
    17                     }
    18             });
    19     try {
    20         downloader.startDownload(); // 开始下载
    21     } catch (Exception e) {
    22         e.printStackTrace();
    23     }
  • 相关阅读:
    NOIP2016-2020 复盘
    「笔记」线段树合并/分裂
    「笔记」线性基
    20210628模拟赛解题报告
    「笔记」左偏树
    题解 CF718C Sasha and Array
    一些杂碎的知识点
    20210614 模拟赛
    洛谷 P4249 [WC2007]剪刀石头布
    CF132E Bits of merry old England
  • 原文地址:https://www.cnblogs.com/tiantianbyconan/p/2919132.html
Copyright © 2011-2022 走看看