您好,登錄后才能下訂單哦!
剛學了下多線程的下載,可能是初次接觸的原因吧,理解起來覺得稍微有點難。所以想寫一篇博客來記錄下,加深自己理解的同時,也希望能夠幫到一些剛接觸的小伙伴。由于涉及到網絡的傳輸,那么就會涉及到http協議。建議在讀本文之前您對http協議有一定的了解。
線程可以通俗的理解為下載的通道,一個線程就是文件下載的一個通道,多線程就是同時打開了多個通道對文件進行下載。當服務器提供下載服務時,用戶之間共享帶寬,在優先級相同的情況下,總服務器會對總下載線程進行平均分配。我們平時用的迅雷下載就是多線程下載。
1: 獲取目標文件的大小(totalSize)
按照常識,我們在下載一個文件之前,通常情況下是要知道該文件的大小,這樣才好在本地留好足量的空間來存儲,免得出現還未下載完,存儲空間就爆了的情況。為了方便代碼的演示,本文在本地tomcat服務器的webapps/ROOT目錄下新建一個test.txt的文件,里面存儲了0123456789這10字節的數據。
2: 確定要開啟幾個線程(threadCount)
需要的文件在服務器上,那我們要開通幾個通道去下載呢?一般情況下這是由CPU去決定的,但是CPU開啟線程的數目也是有限的,不是想開幾個線程就開幾個線程。所開線程的最大數量=(CPU核數+1),例如你的CPU核數為4,那么電腦最多可以開啟5條線程。為了方便代碼演示,本文的threadCount=3
3: 計算平均每個線程需要下載多少個字節的數據(blockSize)
理想情況下多線程下載是按照平均分配原則的,即:單線程下載的字節數等于文件總大小除以開啟的線程總條數,當不能整除時,則最后開啟的線程將剩余的字節一起下載。例如:本文中的totalSize=10,threadCount=3,則前兩個開啟的線程下載3KB的數據,第三個開啟的線程需要下載(3+1)KB的數據。
4:計算各個線程要下載的字節范圍。
平時我們做項目講究分工明確,同理多線程下載也需要明確各個下載的字節范圍,這樣才能將文件高效、快速、準確的下載下來。即在下載過程中,各個線程都要明確自己的開始索引(startIndex)和結束索引(endIndex)。
從上圖我們可以總結出一個公式:
startIndex = threadId乘以blockSize;
endIndex = (threadId+1)乘以blockSize-1;
如果是最后一條線程,那么結束索引為:
endIndex = totalSize - 1;
5: 使用for循環開啟3個子線程
//每次循環啟動一條線程下載
for(int threadId=0; threadId<3;threadId++){
/**
* 計算各個線程要下載的字節范圍
*/
//開始索引
int startIndex = threadId * blockSize;
//結束索引
int endIndex = (threadId+1)* blockSize-1;
//如果是最后一條線程(因為最后一條線程可能會長一點)
if(threadId == (threadCount -1)){
endIndex = totalSize -1;
}
/**
* 啟動子線程下載
*/
new DownloadThread(threadId,startIndex,endIndex).start();
}
6:獲取各個線程的目標文件的開始索引和結束索引的范圍。
告訴服務器,只要目標段的數據,這樣就需要通過Http協議的請求頭去設置(range:bytes=0-499 )
connection.setRequestProperty("range", "bytes="+startIndex+"-"+endIndex);
7:使用RandomAccessFile隨機文件訪問類。創建一個RandomAccessFile對象,將返回的字節流寫到文件指定的范圍
此處有個注意事項:讓RandomAccessFile對象寫字節流之前,需要移動RandomAccessFile對象到指定的位置開始寫。
raf.seek(startIndex);
以上就是多線程下載的大致步驟。代碼如下:
package com.example;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class DownloadTest {
private static final String path = "http://localhost:8080/test.txt";
public static void main(String[] args) throws Exception {
/**
* 1.獲取目標文件的大小
*/
int totalSize = new URL(path).openConnection().getContentLength();
System.out.println("目標文件的總大小為:"+totalSize+"B");
/**
*2. 確定開啟幾個線程
*開啟線程的總數=CPU核數+1;例如:CPU核數為4,則最多可開啟5條線程
*/
int availableProcessors = Runtime.getRuntime().availableProcessors();
System.out.println("CPU核數是:"+availableProcessors);
int threadCount = 3;
/**
* 3. 計算每個線程要下載多少個字節
*/
int blockSize = totalSize/threadCount;
//每次循環啟動一條線程下載
for(int threadId=0; threadId<3;threadId++){
/**
* 4.計算各個線程要下載的字節范圍
*/
//開始索引
int startIndex = threadId * blockSize;
//結束索引
int endIndex = (threadId+1)* blockSize-1;
//如果是最后一條線程(因為最后一條線程可能會長一點)
if(threadId == (threadCount -1)){
endIndex = totalSize -1;
}
/**
* 5.啟動子線程下載
*/
new DownloadThread(threadId,startIndex,endIndex).start();
}
}
//下載的線程類
private static class DownloadThread extends Thread{
private int threadId;
private int startIndex;
private int endIndex;
public DownloadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run(){
System.out.println("第"+threadId+"條線程,下載索引:"+startIndex+"~"+endIndex);
//每條線程要去×××器拿取目標段的數據
try {
//創建一個URL對象
URL url = new URL(path);
//開啟網絡連接
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
//添加配置
connection.setConnectTimeout(5000);
/**
* 6.獲取目標文件的[startIndex,endIndex]范圍
*/
//告訴服務器,只要目標段的數據,這樣就需要通過Http協議的請求頭去設置(range:bytes=0-499 )
connection.setRequestProperty("range", "bytes="+startIndex+"-"+endIndex);
connection.connect();
//獲取響應碼,注意,由于服務器返回的是文件的一部分,因此響應碼不是200,而是206
int responseCode = connection.getResponseCode();
//判斷響應碼的值是否為206
if (responseCode == 206) {
//拿到目標段的數據
InputStream is = connection.getInputStream();
/**
* 7:創建一個RandomAccessFile對象,將返回的字節流寫到文件指定的范圍
*/
//獲取文件的信息
String fileName = getFileName(path);
//rw:表示創建的文件即可讀也可寫。
RandomAccessFile raf = new RandomAccessFile("d:/"+fileName, "rw");
/**
* 注意:讓raf寫字節流之前,需要移動raf到指定的位置開始寫
*/
raf.seek(startIndex);
//將字節流數據寫到file文件中
byte[] buffer = new byte[1024];
int len = 0;
while((len=is.read(buffer))!=-1){
raf.write(buffer, 0, len);
}
//關閉資源
is.close();
raf.close();
System.out.println("第 "+ threadId +"條線程下載完成 !");
} else {
System.out.println("下載失敗,響應碼是:"+responseCode);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//獲取文件的名稱
private static String getFileName(String path){
//http://localhost:8080/test.txt
int index = path.lastIndexOf("/");
String fileName = path.substring(index+1);
return fileName ;
}
}
示例代碼運行結果如下:
目標文件的總大小為:10B
CPU核數是:4
第0個線程,下載索引:0~2
第1個線程,下載索引:3~5
第2個線程,下載索引:6~9
第1個線程下載完成!
第2個線程下載完成!
第0個線程下載完成!
好了,本文寫到此為止。以上是我個人對多線程下載的初步理解,如有不妥之處,還望大家多多指點,感謝!讓我們共同學習,一起進步。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。