java中多線程下載

多線程下載可以搶佔其它相同優先級用戶的網絡資源(寬帶),所以說下載速度比較快,迅雷、快播都使用了多線程下載。
1.請求服務器上的文件的長度
2.根據服務器上的文件長度在手機上創建一個一模一樣大小的文件
3.根據線程的個數和文件的長度來計算每一個線程需要下載的範圍
           文件的長度爲:10  線程的數量爲:3    每一塊的大小:10/3=3.3333=3
            線程0:0~2
            線程1:3~5
            線程2:6~9
                
                    開始的位置:線程的id*每一塊的大小。
                    結束的位置:線程的(id+1 ) *每一塊的大小 -1;
                     最後一個線程結束的位置是:文件的長度-1;   

4.開啓多個線程,每一個線程下載自己的那塊範圍
    1).請求服務器上傳的部分內容。
                Range(請求頭): bytes=3-8獲取服務器文件3-8的內容
                特點:服務器返回值不再是200,是206.
    2).把寫入的位置移動到線程開始的位置上。
    3).循環的讀取內容寫入到本地。


####斷點下載(非正常關閉)
*在每一次讀取到數據,寫入本地數據時,應該把總記錄存到本地。
*當下一次開始下載時,先查看有沒有下載的記錄(存檔),如果有就取出來,繼續下載

代碼:

public class DownloadUtils {
	// 完成的線程
	private int completeThread;
	private int threadCount;

	/**
	 * 開始多線程下載
	 * 
	 * @param url
	 *            訪問的服務器地址
	 * @param dir
	 *            本地接收路徑
	 * @param threadCount
	 *            開啓多少條線程下載
	 */
	public void startMultiThreadDownload(final String url, final String dir,
			final int threadCount) {
		this.threadCount = threadCount;
		completeThread = 0;
		new Thread(new Runnable() {

			@Override
			public void run() {
				// 1.請求服務器上的文件的長度
				int fileLength = getRemoteServiceFileLength(url);
				if (fileLength == -1) {
					System.out.println("請求服務器文件長度失敗,停止下載");
					return;
				}
				System.out.println("遠程服務器的長度爲:" + fileLength);

				// 2.根據服務器上的文件長度在手機上創建一個一模一樣大小的文件
				String fileName = url.substring(url.lastIndexOf("/") + 1);
				File file = new File(dir, fileName);
				try {
					RandomAccessFile raf = new RandomAccessFile(file, "rwd");
					raf.setLength(fileLength);
					raf.close();
					System.out.println("本地文件創建成功" + file.getPath());
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				// 3.根據線程的個數和文件的長度來計算每一個線程需要下載的範圍
				int blockSize = fileLength / threadCount;
				for (int i = 0; i < threadCount; i++) {
					// 開始的位置:線程的id*每一塊的大小。

					int start = i * blockSize;
					// 結束的位置:線程的(id+1 ) *每一塊的大小 -1;
					int end = (i + 1) * blockSize - 1;
					// 最後一個線程結束的位置是:文件的長度-1;
					if ((i == threadCount - 1)) {
						end = fileLength - 1;
					}
					// 4.開啓多個線程,每一個線程下載自己的那塊範圍,傳遞參數:下載地址,存儲文件的路徑
					// 開始的位置,結束的位置,線程ID
					new Thread(new DownloadRunnable(url, file.getPath(), start,
							end, i)).start();

				}

			}
		}).start();

	}

	/**
	 * 下載任務類
	 * 
	 * @author Administrator
	 * 
	 */
	class DownloadRunnable implements Runnable {
		private String url;
		private File localFile; // 文件的路徑
		private int start; // 當前線程開始下載的位置
		private int end; // 結束下載的位置
		private int threadID;// 線程ID

		public DownloadRunnable(String url, String path, int start, int end,
				int i) {
			this.url = url;
			this.localFile = new File(path);
			this.start = start;
			this.end = end;
			this.threadID = i;
		}

		@Override
		public void run() {
			HttpURLConnection conn = null;

			try {
				// 添加斷點下載
				int total = 0;// 當前線程下載的總進度
				// 把總進度值存儲到本地去
				File cacheFile = new File(localFile.getParent(), "thread"
						+ threadID + ".hm");

				RandomAccessFile cacheRAF = new RandomAccessFile(cacheFile,
						"rwd");

				// 判斷上一次是否有緩存的進度
				if (cacheFile.exists() && cacheFile.length() > 0) {// 文件存在並且有數據
					// 上一次下載的進度
					int progress = Integer.valueOf(cacheRAF.readLine());
					start += progress;// 把上一次的進度在開始的位置加上,繼續下載
					total = progress;// 把上一次下載總進度賦值
				}
				System.out.println("線程" + threadID + ",開始下載了,下載的範圍是:" + start
						+ " ~" + end);
				conn = (HttpURLConnection) new URL(url).openConnection();
				conn.setRequestMethod("GET");
				conn.setConnectTimeout(5000);
				conn.setReadTimeout(5000);
				// 設置請求頭消息:Range,請求服務器上文件的部分內容
				conn.setRequestProperty("Range", "bytes=" + start + "-" + end);

				int responseCode = conn.getResponseCode();

				if (responseCode == 206) {
					int contentLength = conn.getContentLength();
					System.out.println("線程" + threadID + ",請求成功,內容長度爲:"
							+ contentLength);
					// 得到服務器返回的數據
					InputStream is = conn.getInputStream();
					RandomAccessFile raf = new RandomAccessFile(localFile,
							"rwd");
					// 把寫入的位置移動到線程開始的位置上
					raf.seek(start);
					byte[] buf = new byte[1024];
					int len = 0;

					while ((len = is.read(buf)) != -1) {
						raf.write(buf, 0, len);
						total += len;
						cacheRAF.seek(0);// 每一次都移動到文件的開始位置進行寫入
						cacheRAF.write(String.valueOf(total).getBytes());

					}
					cacheRAF.close();
					raf.close();
					is.close();
					System.out.println("線程" + threadID + ",下載完成了.");
					completeThread++;
					if (completeThread == threadCount) {
						System.out.println("全部下載完整,刪除臨時配置文件.");
						for (int i = 0; i < threadCount; i++) {
							File deleteFile = new File(localFile.getParent(),
									"thread" + i + ".hm");
							System.out.println(deleteFile.delete());
						}
					}

				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();

			} finally {
				if (conn != null) {
					conn.disconnect();
				}
			}

		}
	}

	/**
	 * 根據URL連接請求遠程服務器獲取文件大小
	 * 
	 * @param url
	 *            服務器地址
	 * @return
	 */
	private int getRemoteServiceFileLength(String url) {
		HttpURLConnection conn = null;
		try {
			conn = (HttpURLConnection) new URL(url).openConnection();
			// 請求方式,必須大小
			conn.setRequestMethod("GET");
			// 連接超時
			conn.setConnectTimeout(5000);
			// 讀取超時
			conn.setReadTimeout(5000);

			int reponseCode = conn.getResponseCode();// 獲取的服務器返回的響應碼
			if (reponseCode == 200) {
				return conn.getContentLength();
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				conn.disconnect();
			}
		}

		return -1;
	}
}


調用:
public class Demo {
	public static void main(String[] args) {
		System.out.println("開啓多線程下載了............");

		// 鏈接,路徑,線程的數量
	
		String url = "http://192.168.1.109/FeiQ.exe";
		String dir = "F:/WDJDownload";
		int threadCount = 3;
		DownloadUtils du = new DownloadUtils();
		du.startMultiThreadDownload(url, dir, threadCount);
	}
}

發佈了31 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章