[工具库]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 }

 

 



来源博客:Wang Jie's Blog's Blog
本文链接:https://blog.wangjiegulu.com/2013/02/20/工具库-JFileDownloader工具类——多线程下载网络文件,并保存在本地/
版权声明:本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处。