【Java】網絡編程——多線程下載文件

前言

多線程下載文件,比單線程要快,當然,線程不是越多越好,這和獲取的源文件還有和網速有關。

下載流程(代碼片段)

1.  根據訪問的URL路徑調用openConnection()獲得HttpURLConnection對象,接着調用getContentLengthLong()方法獲得文件的  字節大小,然後通過RandomAccessFile對象調用setLength()設置本地文件的長度。(這個文件是null數據文件,通過多線程進行對RandomAccessFile對象的本地文件隨機位置寫入數據)

URL url = new URL(str_url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
long fileLength = conn.getContentLengthLong(); // 得到需要下載的文件大小
RandomAccessFile file = new RandomAccessFile(storagePath, "rwd");
file.setLength(fileLength); // 關鍵方法 : 設置本地文件長度
file.close();
conn.disconnect();

2. 根據獲得的文件長度,計算每條線程下載的起始位置與結束位置,因爲不一定平均分,所以最後一條線程下載剩餘的字節

long oneThreadReadByteLength = fileLength / threadNumber;
for (int i = 0; i < threadNumber; i++) {
	long startPosition = i * oneThreadReadByteLength;
	long endPosition = i == threadNumber - 1 ? fileLength : (i + 1) * oneThreadReadByteLength - 1;
}

3. 每條線程請求的範圍參數設置,請求頭參數 : Range:bytes=0-length

URL url = new URL(str_url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); // 關鍵方法: 每條線程請求的字節範圍

4. 每條線程存儲數據到文件的起始位置設置(RandomAccessFile的seek()方法),以及響應碼206判斷

if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) { // 關鍵響應碼 :206,請求成功 + 請求數據字節範圍成功
	RandomAccessFile file = new RandomAccessFile(storagePath, "rwd");
	file.seek(startPosition); // 關鍵方法 :每條線程起始寫入文件的位置
	InputStream in = conn.getInputStream();
	byte[] buf = new byte[8192];
	int len;
	while ((len = in.read(buf)) > 0) {
		file.write(buf, 0, len);
	}
}

完整代碼

main.java

public class Main {
	
	public static void main(String[] args) throws Exception {
		
		String path = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588346633185&di=8f8b2b357c8461d232fcce9e0c476f3a&imgtype=0&src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2Fe79d2c22d40a801e7b02183dee9db3e5c71514af.jpg";
		MultiThreadDownload mtd = new MultiThreadDownload(path, "G:\\LeiMus.jpg", 3);
		mtd.download();
		
	}

}

MultiThreadDownload.java

package com.bin.demo;

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

public class MultiThreadDownload {
	
	private String str_url;
	private String storagePath;
	private int threadNumber;
	private static long downloadByteCount;
	
	MultiThreadDownload(String str_url, String storagePath, int threadNumber) {
		this.str_url = str_url;
		this.storagePath = storagePath;
		this.threadNumber = threadNumber;
	}
	
	public void download() throws IOException, InterruptedException {
		long startTime = System.currentTimeMillis();
		System.out.println("Download......");
		
		/*
		 *  首先設置本地文件的大小
		 *  當然這是個null數據的文件
		 *  這樣才能通過RandomAccessFile的數組下標機制達到隨機位置寫入
		 */
		URL url = new URL(str_url);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setConnectTimeout(10000);
		conn.setRequestMethod("GET");
		long fileLength = conn.getContentLengthLong(); // 得到需要下載的文件大小
		conn.disconnect();
		RandomAccessFile file = new RandomAccessFile(storagePath, "rwd");
		file.setLength(fileLength); // 關鍵方法 : 設置本地文件長度
		file.close();
		
		/*
		 *  計算每條線程下載的字節數,以及每條線程起始下載位置與結束的下載位置,
		 *  因爲不一定平均分,所以最後一條線程下載剩餘的字節
		 *  然後創建線程任務並啓動
		 *  Main線程等待每條線程結束(join()方法)
		 */
		long oneThreadReadByteLength = fileLength / threadNumber;
		for (int i = 0; i < threadNumber; i++) {
			long startPosition = i * oneThreadReadByteLength;
			long endPosition = i == threadNumber - 1 ? fileLength : (i + 1) * oneThreadReadByteLength - 1;
			Thread t = new Thread(new Task(startPosition, endPosition));
			t.start();
			t.join();
		}
		
		/*
		 *  檢查文件是否下載完整,不完整則刪除
		 */
		if (downloadByteCount == fileLength) {
			System.out.println("ALL Thread Download OK.");
			System.out.println("time = " + ((System.currentTimeMillis() - startTime) / 1000) + " S");
		} else {
			System.out.println("Download Error.");
			new File(storagePath).delete();
		}
	}
	
	class Task implements Runnable {
		
		private long startPosition;
		private long endPosition;
		
		Task(long startPosition, long endPosition) {
			this.startPosition = startPosition;
			this.endPosition = endPosition;
		}

		@Override
		public void run() {
			try {
				URL url = new URL(str_url);
				HttpURLConnection conn = (HttpURLConnection) url.openConnection();
				conn.setConnectTimeout(10000);
				conn.setRequestMethod("GET");
				conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); // 關鍵方法: 每條線程請求的字節範圍
				if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) { // 關鍵響應碼 :206,請求成功 + 請求數據字節範圍成功
					RandomAccessFile file = new RandomAccessFile(storagePath, "rwd");
					file.seek(startPosition); // 關鍵方法 :每條線程起始寫入文件的位置
					InputStream in = conn.getInputStream();
					byte[] buf = new byte[8192];
					int len;
					while ((len = in.read(buf)) > 0) {
						file.write(buf, 0, len);
						downloadByteCount += len;
					}
					// 關閉網絡連接及本地流
					in.close();
					file.close();
					conn.disconnect();
					System.out.println(Thread.currentThread().getName() + ": download OK");
				}
			} catch (IOException e) {
				System.out.println(Thread.currentThread().getName() + "_Error : " + e);
			}
		}
		
	}

}

輸出:

Download......
Thread-0: download OK
Thread-1: download OK
Thread-2: download OK
ALL Thread Download OK.
time = 1 S

我下載的是百度圖片,另外多線程下載的結果時間和網速和文件大小有關。

附加我的結果圖片:

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章