博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[工具库]JFileDownloader工具类——多线程下载网络文件,并保存在本地
阅读量:7135 次
发布时间:2019-06-28

本文共 17402 字,大约阅读时间需要 58 分钟。

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

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

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

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

 

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

本文转载,来自:

 

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      * 默认使用的线程数量。
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 *
    59 *
  • 检验URL的合法性
    60 *
  • 计算下载所需的线程数量和每个线程需下载多少大小的文件
    61 *
  • 启动各线程。 62 *
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 wangjie114 */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 wangjie130 * @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 wangjie140 * @param destFilePath 要保存的文件目标(路径+文件名)141 * @return 返回当前JFileDownloader对象142 */143 public JFileDownloader setDestFilePath(String destFilePath) {144 this.destFilePath = destFilePath;145 return this;146 }147 /**148 * 设置下载该文件所需要的线程数量。149 * @author wangjie150 * @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 wangjie170 * @param fileDownloadListener 要移除的监听器对象171 */172 public void removeFileDownloadListener(173 JFileDownloadListener fileDownloadListener) {174 fileDownloadListener = null;175 }176 177 178 /**179 * 检验设置的参数是否合法。180 * @author wangjie181 * @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 wangjie10  * @version 创建时间:2013-2-7 下午2:12:4511  */12 public interface JFileDownloadListener {13     /**14      * 该方法可获得文件的下载进度信息。15      * @author wangjie16      * @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 wangjie24      * @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 wangjie119      */120     public synchronized void notificationProgress(){121         notificationTag = true;122     }123     /**124      * 取消该通知线程125      * @author wangjie126      */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 wangjie105      */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     }

 

 

你可能感兴趣的文章
PostgreSQL数据库中获取表主键名称
查看>>
js判断页面点击事件
查看>>
JavaScript_JS判断客户端是否是iOS或者Android
查看>>
真正的轻量级WebService框架——使用JAX-WS(JWS)发布WebService
查看>>
Setting up a Passive FTP Server in Windows Azure VM(ReplyCode: 227, Entering Passive Mode )
查看>>
Bash简单介绍
查看>>
深入理解JavaScript系列(50):Function模式(下篇)
查看>>
修改数据库端口为51433
查看>>
程序员必须掌握的6种软技能
查看>>
Docker学习笔记
查看>>
Linux设备模型(热插拔、mdev 与 firmware)【转】
查看>>
Spring注解配置
查看>>
奇怪的问题,列名无效
查看>>
转: MySQL索引原理及慢查询优化 (from 美团技术博客)
查看>>
Cannot change version of project facet Dynamic Web Module to 3.0 异常问题处理
查看>>
使用 Python 创建你自己的 Shell (上)
查看>>
机器学习教程 一-不懂这些线性代数知识 别说你是搞机器学习的
查看>>
NOIP2012pj摆花[DP 多重背包方案数]
查看>>
phoenix 开发API系列(三)phoenix api 结合数据库
查看>>
用CSS text-transform转换字母大小写
查看>>