前言
多線程下載文件,比單線程要快,當然,線程不是越多越好,這和獲取的源文件還有和網速有關。
下載流程(代碼片段)
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
我下載的是百度圖片,另外多線程下載的結果時間和網速和文件大小有關。
附加我的結果圖片: