什么是多線程下載
舉例: 一個裝有水的水桶(要下載的資源)霉翔,出水口(下載線程)揣炕,出水口越多,水流的越快稠腊。即多個線程同時去下載一個文件躁染,效率更高。
需求分析
- 對下載資源分塊(對應(yīng)下載線程的個數(shù))
每一塊下載資源的大小對應(yīng)為:block_size = total_size / threadCount
如總大小為10架忌,下載線程有三個吞彤,則每塊大小為3,每個線程需要下載的資源位置為:1-3叹放,4-6饰恕,7-10
- http請求屬性設(shè)置從指定位置下載資源
要保證每個線程從資源的指定的位置下載的話,可以通過設(shè)置http請求的Range關(guān)鍵字:setRequestProperty("Range", "bytes=startSize-endSize")
- 在硬盤預(yù)先創(chuàng)建一個和下載資源相同大小的文件
不僅下載要從指定位置開始下井仰,寫入文件同樣也要從文件的指定位置寫入埋嵌,這一功能可以通過 RandomAcessFile 實現(xiàn):
randomAccessFile.setLength(long size) 設(shè)置文件大小
randomAccessFile.seek(long pos)
- 實時記錄每個線程下載的進度
為了保證中斷下次能繼續(xù)下載,需要實時記錄下每個線程正在寫入文件的位置俱恶,保證下次能繼續(xù)從這個位置下載并寫入雹嗦。
關(guān)鍵代碼
- 對資源分塊、預(yù)先創(chuàng)建同資源大小文件
//建立連接合是,獲取資源大小
HttpURLConnection conn = (HttpURLConnection)
new URL(address).openConnection();
//····設(shè)置http請求的其他屬性
//創(chuàng)建同資源大小文件
long total_size = conn.getContentLength()//下載資源大小
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.setLength(total_size);
//資源分塊
long block_size = total_size / threadCount;
for(int i = 1 ; i <= threadCount ; i++) {
long startSize = (i - 1) * block_size; //不是從1開始了罪,見下注
long endSize = i * block_size - 1;
if(i == threadCount){
endSize = size-1;
}
注:因為http請求資源是從0開始計算的,請求的總產(chǎn)總長度實際是0-total_size-1聪全,所以上面startSize泊藕,endSize的計算是那樣。
- 設(shè)置http請求指定資源位置难礼、文件寫入指定資源位置
//startSize表示下載的起始位置娃圆,endSize表示下載的終止位置
conn.setRequestProperty("Range", "bytes=" + startSize + "-" + endSize);
//設(shè)置文件的寫入位置
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(startSize);//表示從startSize處寫
- 記錄斷點汽久,并在下次開始時讀取
//創(chuàng)建和線程同名的文件,將每個線程下載的位置同步到這個文件中
File positionFile= new File("d:/" + threadId + ".txt");
public void writeToFile(File positionFile){
byte[] bytes = new byte[1024]; //1024表示緩沖區(qū)的大小踊餐,可自行設(shè)置大小
int len = 0;
int total = 0;
while( (len = is.read(bytes)) != -1 ){
//為什么使用這個函數(shù),見注
RandomAccessFile raf = new RandomAccessFile(positionFile, "rwd");
randomAccessFile.write(bytes, 0, len);
total += len;
raf.write(String.valueOf(total).getBytes());//記錄當前下載到的位置
raf.close();
}
}
//如果有斷點記錄文件臀稚,則讀取其中的值吝岭,并更改相應(yīng)的http請求位置和寫入文件位置
if(positionFile.exists() && positionFile.length() > 0){
BufferedReader br = new BufferedReader(new FileReader(positionFile));
int position = Integer.parseInt(br.readLine());
startSize += position; //startSize對應(yīng)當前寫成請求資源的起始位置,
//也是寫入預(yù)留文件的起始位置
}
注:RandomAccessFile raf = new RandomAccessFile(positionFile, "rwd"); 使用這個函數(shù)來進行同步下載位置是因為他會在每次數(shù)據(jù)更新時同步到物理磁盤上吧寺,保證中斷時不會造成數(shù)據(jù)還為寫入磁盤窜管。