簡易多執行緒下載

我叫阿狸貓發表於2014-12-03

多執行緒斷點下載原理:

1.設定Range請求頭,開啟子執行緒後,各歸各下載相應的資料

2.下載的過程中,每個執行緒都有個檔案實時記錄當前下載進度,保證暫停下載後再次繼續可以獲取上次下載的進度。




import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * 
 * @author 多執行緒斷點下載
 *
 */
public class ManyThreadDownload {
	private static int threadTotalCount = 3;							//總共開幾個執行緒執行
	private static String path = "http://127.0.0.1:9090/555.exe";		//下載地址
	private static int runningThreadCount;								//當前正在執行的執行緒個數

	public static void main(String[] args) {
		HttpURLConnection conn = null;
		try {
			URL url = new URL(path);									//URL地址
			conn = (HttpURLConnection) url.openConnection();			//獲取一個連線物件
			conn.setConnectTimeout(3000);								//設定連線超時時間
			conn.setReadTimeout(3000);									//設定已經連上伺服器,但是在讀取資料時候的超時時間
			conn.setRequestMethod("GET");								//設定請求方式
			if (conn.getResponseCode() == 200) {						//請求成功後
				int size = conn.getContentLength();						//獲取伺服器返回的檔案大小
				long blockSize = size / threadTotalCount;				//每個執行緒要下載的大小=伺服器檔案大小/執行緒數
				runningThreadCount = threadTotalCount;					//剛開始的時候將執行緒總數賦值給正在執行的執行緒數
				for (int i = 0; i < threadTotalCount; i++) {			//迴圈開啟執行緒下載
					long startIndex = i * blockSize;					//計算每個執行緒開始位置下載的位置
					long endIndex = (i + 1) * blockSize - 1;			//計算每個執行緒結束位置下載的位置
					if (i == (threadTotalCount - 1)) {					//當迴圈到最後一個執行緒的時候,下載的結束位置就是檔案大總大小
						endIndex = size;
					}
					new DownLoadThread(i, path, startIndex, endIndex).start();//進行下載
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				conn.disconnect();
			}
		}
	}

	private static class DownLoadThread extends Thread {
		private int threadIndex;						//當前執行緒索引號
		private String path;							//下載檔案的url
		private long startIndex;						//開始下載的位置
		private long endIndex;							//下載到哪個位置結束

		public DownLoadThread(int threadIndex, String path, long startIndex, long endIndex) {
			this.threadIndex = threadIndex;
			this.path = path;
			this.startIndex = startIndex;
			this.endIndex = endIndex;
		}

		@Override
		public void run() {
			super.run();
			HttpURLConnection conn = null;
			File currentPositionFile = new File("Thread"+threadIndex+".txt");			//這個檔案是用於記錄每個執行緒下載進度的一個檔案,只儲存下載的位元組數
			/* RandomAccessFile
			 * 用這個類來操作儲存下載進度的檔案,因為將這個類第二個引數設定rwd模式的話,會實時更新硬碟上的資料,而如果用FileOutputStream的話,就算呼叫flush,也只是刷到硬碟上的快取裡,
			 * 並沒有馬上寫到硬碟上
			 */
			RandomAccessFile currentPositionRAF = null;									
			int total = 0;																//當前下載的位元組進度	
			try {
				if(currentPositionFile.exists()&¤tPositionFile.length()>0){		//如果記錄進度的檔案存在的話,就表示之前暫停過
					FileInputStream fis = new FileInputStream(currentPositionFile);		
					InputStreamReader isr = new InputStreamReader(fis);
					BufferedReader br = new BufferedReader(isr);
					String lastPosition = br.readLine();
					if(lastPosition!=null&&!"".equals(lastPosition.trim())){		    //讀取進度檔案,如果讀到的內容不為空
						total = Integer.parseInt(lastPosition);							//將當前執行緒的總進度設定為進度檔案裡的進度	
						startIndex += Integer.valueOf(lastPosition);					//將當前開始進度設定為進度檔案裡的進度(因為是斷點下載)
						System.out.println("執行緒"+threadIndex+",從"+lastPosition+"繼續開始");
					}
					fis.close();														//千萬不要忘記關閉流
					isr.close();														//千萬不要忘記關閉流
					br.close();															//千萬不要忘記關閉流
				}	
				
				URL url = new URL(path);												//URL地址
				conn = (HttpURLConnection) url.openConnection();						//獲取一個連線物件
				conn.setConnectTimeout(3000);											//設定連線超時時間
				conn.setReadTimeout(3000);												//設定已經連上伺服器,但是在讀取資料時候的超時時間
				conn.setRequestMethod("GET");											//設定請求方式
				conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);		//設定訊息頭是斷點下載,告訴伺服器返回指定片段資料
				
				if(conn.getResponseCode()==206){										//帶著Range訊息頭去訪問伺服器,伺服器返回的狀態碼是206
					InputStream is = conn.getInputStream();								//獲取伺服器返回資料的輸入流
					File file = new File("hehe.exe");									//建立本地檔案
					RandomAccessFile raf = new RandomAccessFile(file, "rw");			//建立一個往下載檔案裡寫資料的類
					raf.seek(startIndex);												//設定上次的進度
					int len = 0;														
					byte[] buffer = new byte[1024*1024];
					while((len=is.read(buffer))!=-1){
						total += len;
						currentPositionRAF = new RandomAccessFile(currentPositionFile, "rwd");
						currentPositionRAF.write(String.valueOf(total).getBytes());		//寫進度
						raf.write(buffer, 0, len);
						//記錄檔案下載進度的txt檔案必須是迴圈裡寫一次關一次,因為JVM關閉的時候,不一定是迴圈完畢的時候,這時候進度檔案還是開啟著的
						currentPositionRAF.close();				
					}
					is.close();
					raf.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				synchronized (ManyThreadDownload.class) {
					if(conn!=null){
						conn.disconnect();
					}
					//因為執行到finally時候,肯定是程式已經下載完畢了,所以把當前在執行的程式數減一
					runningThreadCount--;
					if(runningThreadCount==0){
						for (int i = 0; i < threadTotalCount; i++) {			//刪除記錄每個執行緒進度的臨時檔案
							File file = new File("Thread"+i+".txt");
							try {
								System.out.println(file.delete());
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					}
				}
			}
		}
	}
}


相關文章