本人大四即將畢業的准程序員(JavaSE、JavaEE、android等)一枚,小項目也做過一點,於是乎一時興起就寫了一些工具。
我會在本博客中陸續發布一些平時可能會用到的工具。
代碼質量可能不是很好,大家多擔待!
代碼或者思路有不妥之處,還希望大牛們能不吝賜教哈!
以下代碼為本人原創,轉載請注明:
本文轉載,來自:http://www.cnblogs.com/tiantianbyconan/archive/2013/02/20/2919132.html
JFileDownloader:用於多線程下載網絡文件,並保存在本地。
源碼如下:
1.JFileDownloader類:主要負責下載的初始化可啟動工作。

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下載的進度。

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類:該線程為通知下載進度的線程。

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類:真正的下載線程,該線程用於執行該線程所要負責下載的數據。

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 }