1. 程式人生 > >多線程下載,以及斷點的實現

多線程下載,以及斷點的實現

popu href 狀態 ktr server tle 找到 row 下載

技術分享圖片

技術分享圖片

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

/**
 * 多線程下載,以及斷點下載的實現<br>
 * 當中有個不好的地方,<br>
 * 就是進度文件的保存的時候假設採用RandomAccessFile的方式進行保存的時候<br>
 * 盡管會將文件的進度時時的保存在進度文件裏,<br>
 * 可是,經過實際的測試這樣會大大的減少文件的下載的速度,<br>
 * 假設採用File和FileOutputStream的話盡管能夠加快下載的速度<br>
 * 可是進度文件的時時寫入會出現故障.<br>
 * 
 * <br>
 * 眼下我還沒有找到非常好的解決方案,假設大家有的話歡迎給我留言.
 * 
 * @author MartinDong
 * 
 */
public class Demo {
	// 定義線程個數
	public static int threadCount = 3;
	// 定義當前存貨的線程個數
	public static int runningThread = 3;

	public static void main(String[] args) throws Exception {
		// 1,連接到server,獲取一個文件,獲取文件的大小跟server的文件一樣的暫時文件
		String path = "http://172.22.64.193:8080/test.exe";
		URL url = new URL(path);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		// 設置超時
		conn.setConnectTimeout(5000);
		// 設置請求方式
		conn.setRequestMethod("GET");
		// 獲取server的返回碼
		int code = conn.getResponseCode();
		// 推斷返回碼
		if (code == 200) {
			// 獲取返回的長度
			int length = conn.getContentLength();
			System.out.println("文件總長度:" + length);

			// 在client創建出一個跟server大小一致的暫時文件
			RandomAccessFile raf = new RandomAccessFile("test.exe", "rwd");
			// 指定暫時文件的大小
			raf.setLength(length);
			// 釋放資源
			raf.close();

			// 平均每個線程的文件大小
			int blockSize = length / threadCount;

			for (int threadId = 1; threadId <= threadCount; threadId++) {
				// 線程開始的下載位置
				int startIndex = (threadId - 1) * blockSize;
				// 線程的結束位置
				int endIndex = threadId * blockSize - 1;
				// 推斷是否是最後一個線程
				if (threadId == threadCount) {
					// 設置結束的位置為到文件的最後
					endIndex = length;
				}
				System.out.println("線程:" + threadId + "下載:>>>>>>>>"
						+ startIndex + ">>>>>>>>>>" + endIndex);

				new DownlodeThread(path, threadId, startIndex, endIndex)
						.start();
			}
		}
	}

	/**
	 * 下載文件的子線程類,每個線程下載相應位置文件數據
	 * 
	 * @author MartinDong
	 * 
	 */
	public static class DownlodeThread extends Thread {
		private String path;
		private int threadId;
		private int startIndex;
		private int endIndex;

		/**
		 * 
		 * @param path
		 *            文件的下載路徑
		 * @param threadId
		 *            線程id
		 * @param startIndex
		 *            線程開始的位置
		 * @param endIndex
		 *            線程結束的位置
		 */
		public DownlodeThread(String path, int threadId, int startIndex,
				int endIndex) {
			this.path = path;
			this.threadId = threadId;
			this.startIndex = startIndex;
			this.endIndex = endIndex;
		}

		@Override
		public void run() {
			try {
				// 檢查是否存在下載歷史的文件
				File tempFile = new File(threadId + ".txt");// =========================斷點記錄操作===============================
				if (tempFile.exists() && tempFile.length() > 0) {
					// 文件輸入流
					FileInputStream fis = new FileInputStream(
							tempFile);
					// 中間變量,緩存的作用
					byte[] tempBuffer = new byte[1024];
					// 獲取進度文件的數據大小
					int length = fis.read(tempBuffer);
					// 獲取進度文件的數據
					String historyData = new String(tempBuffer, 0, length);
					// 將進度數據裝換為整型
					int historyDataInt = Integer.parseInt(historyData);
					// 改動真正的下載位置
					startIndex = historyDataInt;
					fis.close();
				}// =========================斷點記錄操作===============================

				// 將地址轉換為URL
				URL url = new URL(path);
				// 獲取http連接
				HttpURLConnection conn = (HttpURLConnection) url
						.openConnection();
				// 設置連接的請求方式
				conn.setRequestMethod("GET");
				// 重要:請求server下載部分的文件,指定文件的位置
				conn.setRequestProperty("Range", "bytes=" + startIndex + "-"
						+ endIndex);

				System.out
						.println("線程:" + threadId + "真實開始的下載進度:" + startIndex);
				// 設置超時時間
				conn.setReadTimeout(5000);
				// 得到server的狀態碼,200表示請求的所有資源得到響應=== ok,206請求的部分資源得到響應=== ok
				int code = conn.getResponseCode();
				System.out.println("code:" + code);

				if (code == 206) {
					// 返回的是指定位置的文件流
					InputStream is = conn.getInputStream();
					// 創建一個暫時的文件
					RandomAccessFile raf = new RandomAccessFile("test.exe",
							"rwd");
					// 移動指針,到指定的文件位置,
					raf.seek(startIndex);

					// 創建中間緩沖字節數組
					byte[] buffer = new byte[1024];
					// 讀取文件的大小
					int length = 0;

					// 定義已經下載的數據長度,用作斷點下載的記錄=========================斷點記錄操作===============================
					int downlodeTotal = 0;
					// 循環寫入
					while ((length = is.read(buffer)) != -1) {
						// 定義一個記錄線程的記錄文件=========================斷點記錄操作===============================
						RandomAccessFile historyFile = new RandomAccessFile(
								threadId + ".txt", "rwd");
						// 向文件裏寫入數據
						raf.write(buffer, 0, length);
						// 記錄已經下載的文件長度
						downlodeTotal += length;
						// 將已經下載的文件長度和開始的讀取位置相加,得到已經讀取的文件位置
						historyFile.write((downlodeTotal + startIndex + "")
								.getBytes());
						historyFile.close();// =========================斷點記錄操作===============================
						System.out.println("線程:" + threadId + "已下載:"
								+ downlodeTotal);
					}
					is.close();
					raf.close();
					System.out.println("線程:" + threadId + "完成下載............");

				} else {
					System.out.println("線程:" + threadId
							+ "下載失敗請又一次下載............");
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				// 進行線程數量的變化操作
				runningThread--;
				// 假設當前存活的線程為0,運行進度文件統一銷毀的操作
				if (runningThread == 0) {
					// 假設完成下載,清除進度文件
					for (int threadIndex = 1; threadIndex <= threadCount; threadIndex++) {
						// 這裏創建的是與線程文件相應的文件,文件名稱能夠自己定義,方便起見是採用的是線程的ID表示
						File temp = new File(threadIndex + ".txt");
						// 運行文件刪除的操作
						temp.delete();
					}
					System.out.println("文件完成下載,刪除進度文件.............");
				}
			}
		}
	}

}


多線程下載,以及斷點的實現