分片下載
所謂分片下載就是要利用多執行緒的優勢,將要下載的檔案一塊一塊的分配到各個執行緒中去下載,這樣就極大的提高了下載速度。
技術難點
並不能說是什麼難點,只能說沒接觸過不知道罷了。
1、如何請求才能拿到資料的特定部分,而非全部?
可以在HTTP請求頭中加入Range來標識資料的請求範圍/區間,從HTTP/1.1開始可用。
基本用法:
Range: bytes=10-
:取第10個位元組及後所有資料。
Range: bytes=40-100
:取第40個位元組到第100個位元組之間的資料。
這樣我們就能拿到特定部分的資料了,斷點續傳也可以用這個來實現。
PS:0為開始點。
2、分片後某執行緒下載時如何寫出?
思路1:等所有下載完成後進行統一彙總整理然後再一次性寫出。
這簡直是最笨的思路了,如果檔案過大全部拉到記憶體中,豈不涼涼。
思路2:下載採用多執行緒,寫出時採取資料前後順序排隊寫出。
也就是說多執行緒下載,單執行緒輸出,某種程度解決了記憶體佔用問題,不過效率基本不理想。
思路3:要說還是API香,老大哥Java給我們提供了一個類叫做RandomAccessFile
。
這個類可以進行隨機檔案讀寫,其中有一個seek函式,可以將指標指向任意位置,然後進行讀寫。什麼意思呢,舉個例子:假如我們開了30個執行緒,首先第一個下載完成的是執行緒X,它下載的資料範圍是4000-9000,那麼這時我們呼叫seek函式將指標撥動到4000,然後呼叫它的write函式將byte寫出,這時4000之前都是NULL,4000之後就是我們插入的資料。這樣就可以實現多執行緒下載和本地寫入了。
具體實現
分片下載類,我們需要建立多個物件來進行下載。
public class UnitDownloader implements Runnable {
private int from;
private int to;
private File target;
private String uri;
private int id;
public UnitDownloader(int from, int to, File target, String uri, int id) {
this.from = from;
this.to = to;
this.target = target;
this.uri = uri;
this.id = id;
}
public int getFrom() {
return from;
}
public int getTo() {
return to;
}
@Override
public void run() {
//download and save data
try {
HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
connection.setRequestProperty("Range", "bytes=" + from + "-" + to);
connection.connect();
int totalSize = connection.getContentLength();
InputStream inputStream = connection.getInputStream();
RandomAccessFile randomAccessFile = new RandomAccessFile(target, "rw");
randomAccessFile.seek(from);
byte[] buffer = new byte[1024 * 1024];
int readCount = inputStream.read(buffer, 0, buffer.length);
while (readCount > 0) {
totalSize -= readCount;
System.out.println("分片:" + this.id + "的剩餘:" + totalSize);
randomAccessFile.write(buffer, 0, readCount);
readCount = inputStream.read(buffer, 0, buffer.length);
}
inputStream.close();
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製程式碼
分片下載管理器,主要就是拿到內容的總大小,將其分配給每一個UnitDownloader
。這裡的threadCount函式可以再考慮優化一下。
public class MultipleThreadDownloadManager implements Runnable {
private String uri;
private File target;
public MultipleThreadDownloadManager(String uri, File target) {
this.target = target;
this.uri = uri;
if (target.exists() == false) {
try {
target.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 開始下載
*/
public void start() {
new Thread(this).start();
}
/**
* 根據檔案總大小計算執行緒數量
*
* @param totalSize
* @return
*/
public int threadCount(int totalSize) {
if (totalSize < 30 * 2014 * 1024) {
return 1;
}
return 30;
}
@Override
public void run() {
//獲取檔案總大小
int totalSize = 0;
try {
HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
connection.connect();
int contentLength = connection.getContentLength();
totalSize = contentLength;
} catch (IOException e) {
e.printStackTrace();
}
//將檔案分片並分開下載
int threadCount = threadCount(totalSize);
int perThreadSize = totalSize / threadCount;//每一個執行緒分到的任務下載量
int id = 0;
int from = 0, to = 0;
while (totalSize > 0) {
id++;
//計算分片
if (totalSize < perThreadSize) {
from = 0;
to = totalSize;
} else {
from = totalSize;
to = from + perThreadSize;
}
//開始下載
UnitDownloader downloader = new UnitDownloader(from, to, target, uri, id);
new Thread(downloader).start();
}
}
}
複製程式碼