一.基礎知識
1.什么是線程?什么是進程?它們之間的關系?
可以參考之前的一篇文章:java核心知識點學習----並發和並行的區別,進程和線程的區別,如何創建線程和線程的四種狀態,什么是線程計時器
簡單說一個進程可以由多個線程組成,一個操作系統可以多個進程,它們都是可以同時進行工作的.
2.什么是下載?如何多線程進行下載?如何斷點續傳?
廣義上說,凡是在屏幕上看到的不屬於本地計算機上的內容,皆是通過"下載"得來。狹義上人們只認為那些自定義了下載文件的本地磁盤存儲位置的操作才是"下載";。
WEB下載方式分為HTTP與FTP兩種類型,它們分別是Hyper Text Transportation Protocol(超文本傳輸協議)與File Transportation Protocol(文件傳輸協議)的縮寫,它們是計算機之間交換數據的方式,也是兩種最經典的下載方式,該下載方式原理非常簡單,就是用戶兩種規則(協議)和提供文件的服務器取得聯系並將文件搬到自己的計算機中來,從而實現下載的功能。
多線程下載,即是一個文件能過多個線程進行下載;而斷點續傳說的是當一個文件下載到一半時突然由於某個原因下載中斷了,比如突然電腦關機了,那么當再開機時已經下載到一半的文件不需要重頭開始,而是接着下載;其原理很簡單:首先,下載中斷時記住上一個時點下載的位置,然后接着這個位置繼續下載,這個繼續下載可以是人手工觸發的也可以是程序運行時自動識別進行下載的.
3.什么是RandomAccessFile?
RandomAccessFile的唯一父類是Object,與其他流父類不同。是用來訪問那些保存數據記錄的文件的,這樣你就可以用seek( )方法來訪問記錄,並進行讀寫了。這些記錄的大小不必相同;但是其大小和位置必須是可知的。
RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream粘起來,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移動用的seek( ),以及判斷文件大小的length( )。此外,它的構造函數還要一個表示以只讀方式("r"),還是以讀寫方式("rw")打開文件的參數 (和C的fopen( )一模一樣)。它不支持只寫文件,從這一點上看,假如RandomAccessFile繼承了DataInputStream,它也許會干得更好。
所以,本例中是利用RandomAccessFile的seek記住上次的訪問記錄,然后接着上次的訪問進行下載的.
RandomAccess直譯過來是隨機訪問,這樣理解很容易造成困擾,即然是隨機的,那么又怎么來控制進度呢?
由Random這個單詞的示意:
random [ 'rændəm ] adj. 隨意的,任意,隨機的,隨機挑選的,任意隨機的,胡亂任意的,隨機性,任意的,胡亂的,隨便的;(話等)信口亂說的;(人等)偶然碰到的,隨意選擇的,隨便,無關緊要的,漫不經心的
可以看到其還有任意的意思,這就表明可以訪問文件的任意位置,這樣就能解釋為何可以斷點續傳了.
二.程序實現
這里以tomcat的下載為例:http://tomcat.apache.org/download-70.cgi#7.0.54 (tomcat目前最新的版本是7.0.54,2014-07-02)
F12打開chrome的WebDeveloper NetWork窗口,然后點擊下載,如下圖所示:
這里需要注意的是Request Headers里的內容,如RequestMethod,Accept,Accept-Language,Connection等,這里在發出請求時需要將這些東西帶上,這里找到下載tomcat 7.0.54的下載鏈接:http://mirrors.cnnic.cn/apache/tomcat/tomcat-7/v7.0.54/bin/apache-tomcat-7.0.54.zip
實現代碼:DownUtil.java
package com.amos.tool;
import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; /** * Created by amosli on 14-7-2. */
public class DownUtil { // 定義下載資源的路徑 private String path; // 指定所下載的文件的保存位置 private String targetFile; // 定義需要使用多少線程下載資源 private int threadNum; // 定義下載的線程對象 private DownThread[] threads; // 定義下載的文件的總大小 private int fileSize; public DownUtil(String path, String targetFile, int threadNum) { this.path = path; this.threadNum = threadNum; // 初始化threads數組 threads = new DownThread[threadNum]; this.targetFile = targetFile; } public void download() throws Exception { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("Connection", "Keep-Alive"); // 得到文件大小 fileSize = conn.getContentLength(); conn.disconnect(); int currentPartSize = fileSize / threadNum + 1;//這里不必一定要加1,不加1也可以 RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); // 設置本地文件的大小 file.setLength(fileSize); file.close(); for (int i = 0; i < threadNum; i++) { // 計算每條線程的下載的開始位置 int startPos = i * currentPartSize; // 每個線程使用一個RandomAccessFile進行下載 RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); // 定位該線程的下載位置 currentPart.seek(startPos); // 創建下載線程 threads[i] = new DownThread(startPos, currentPartSize, currentPart); // 啟動下載線程 threads[i].start(); } } // 獲取下載的完成百分比 public double getCompleteRate() { // 統計多條線程已經下載的總大小 int sumSize = 0; for (int i = 0; i < threadNum; i++) { sumSize += threads[i].length; } // 返回已經完成的百分比 return sumSize * 1.0 / fileSize; } private class DownThread extends Thread { // 當前線程的下載位置 private int startPos; // 定義當前線程負責下載的文件大小 private int currentPartSize; // 當前線程需要下載的文件塊 private RandomAccessFile currentPart; // 定義已經該線程已下載的字節數 public int length; public DownThread(int startPos, int currentPartSize,RandomAccessFile currentPart) { this.startPos = startPos; this.currentPartSize = currentPartSize; this.currentPart = currentPart; } @Override public void run() { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); InputStream inStream = conn.getInputStream(); // 跳過startPos個字節,表明該線程只下載自己負責哪部分文件。 inStream.skip(this.startPos); byte[] buffer = new byte[1024]; int hasRead = 0; // 讀取網絡數據,並寫入本地文件 while (length < currentPartSize && (hasRead = inStream.read(buffer)) != -1) { currentPart.write(buffer, 0, hasRead); // 累計該線程下載的總大小 length += hasRead; } currentPart.close(); inStream.close(); } catch (Exception e) { e.printStackTrace(); } } } }
測試:DownUtilTest.java
package com.amos; import com.amos.tool.DownUtil; import org.omg.PortableServer.THREAD_POLICY_ID; /** * Created by amosli on 14-7-2. */ public class DownUtilTest { public static void main(String args[]) throws Exception { final DownUtil downUtil = new DownUtil("http://mirrors.cnnic.cn/apache/tomcat/tomcat-7/v7.0.54/bin/apache-tomcat-7.0.54.zip", "tomcat-7.0.54.zip", 3); downUtil.download(); new Thread(new Runnable() { @Override public void run() { while(downUtil.getCompleteRate()<1){ System.out.println("已完成:"+downUtil.getCompleteRate()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
結果:
下載我的頭像:
final com.amos.DownUtil downUtil = new com.amos.DownUtil("http://pic.cnitblog.com/avatar/534352/20131215160918.png", "amosli.png", 2);