本人大四即将毕业的准程序员(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 }