多線程下載
原理:服務器CPU分配給每條線程的時間片相同,服務器帶寬平均分配給每條線程,所以客戶端開啓的線程越多,就能搶佔到更多的服務器資源
單線程下載:從輸入流第0個字節開始讀取,讀取到最後一個字節,把讀取到的數據寫到本地文件中,
寫的時候也要從文件的第0個位置開始寫,寫到最後一個位置多線程的計算:
每個線程預下載的大小: size = 總的大小/線程的數量 (注意,最後一個問題)
size = 10 / 3
id=0線程: 0 -- 2
id=1線程: 3 -- 5
id=2線程: 6 -- 9 (多餘的大小可以給最後一個線程 )
開始位置: start = 線程編號 * 每個線程預下載的大小
start = id * size
結束位置: end =(id+1)*size -1
最後一個線程結束位置:總的大小-1
length-1
(使用Java項目進行多線程下載測試,因android比較麻煩,
Java中搞定移植到android中即可,測試下載的時候最好使用可執行程序,
比如.exe程序,這樣可以知道是否可以真成功)
確定每條線程下載多少數據
發送http請求至下載地址
String path = "http://192.168.1.13:8080/QQ.exe"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(5000); conn.setConnectTimeout(5000); conn.setRequestMethod("GET");
獲取文件總長度,然後創建長度一致的臨時文件
if(conn.getResponseCode() == 200){ //獲得服務器流中數據的長度 int length = conn.getContentLength(); //創建一個臨時文件存儲下載的數據 RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd"); //設置臨時文件的大小 raf.setLength(length); raf.close();
確定線程下載多少數據
//計算每個線程下載多少數據 int blockSize = length / THREAD_COUNT;
計算每條線程下載數據的開始位置和結束位置
for(int id = 1; id <= 3; id++){
//計算每個線程下載數據的開始位置和結束位置
int startIndex = (id - 1) * blockSize;
int endIndex = id * blockSize - 1;
if(id == THREAD_COUNT){
endIndex = length;
}
//開啓線程,按照計算出來的開始結束位置開始下載數據
new DownLoadThread(startIndex, endIndex, id).start();
}
再次發送請求至下載地址,請求開始位置至結束位置的數據
String path = "http://192.168.1.13:8080/QQ.exe";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
//向服務器請求部分數據
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
conn.connect();
* 下載請求到的數據,存放至臨時文件中
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
//指定從哪個位置開始存放數據
raf.seek(startIndex);
byte[] b = new byte[1024];
int len;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
}
raf.close();
}
核心代碼
public class MultiDownload {
static int ThreadCount = 3;
static int finishedThread = 0;
//確定下載地址
static String path = "http://192.168.1.13:8080/QQ.exe";
public static void main(String[] args) {
//發送get請求,請求這個地址的資源
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
if(conn.getResponseCode() == 200){
//拿到所請求資源文件的長度
int length = conn.getContentLength();
File file = new File("local_qq.exe");
//生成臨時文件--佔用磁盤空間--可以防止磁盤空間不夠用
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//設置臨時文件的大小
raf.setLength(length);
raf.close();
//計算出每個線程應該下載多少字節
int size = length / ThreadCount;
for (int i = 0; i < ThreadCount; i++) {
//計算線程下載的開始位置和結束位置
int startIndex = i * size;
int endIndex = (i + 1) * size - 1;
//如果是最後一個線程,那麼結束位置寫死
if(i == ThreadCount - 1){
endIndex = length - 1;
}
// System.out.println("線程" + i + "的下載區間是:" + startIndex + "---" + endIndex);
new DownLoadThread(startIndex, endIndex, i).start();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class DownLoadThread extends Thread{
int startIndex; //下載的開始位置
int endIndex; //下載的結束位置
int threadId; //下載的線程Id
public DownLoadThread(int startIndex, int endIndex, int threadId) {
super();
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
@Override
public void run() {
//再次發送http請求,下載原文件
try {
File progressFile = new File(threadId + ".txt");
//判斷進度臨時文件是否存在
if(progressFile.exists()){
FileInputStream fis = new FileInputStream(progressFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//從進度臨時文件中讀取出上一次下載的總進度,然後與原本的開始位置相加,得到新的開始位置
startIndex += Integer.parseInt(br.readLine());
fis.close();
}
System.out.println("線程" + threadId + "的下載區間是:" + startIndex + "---" + endIndex);
HttpURLConnection conn;
URL url = new URL(MultiDownload.path);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//設置本次http請求所請求的數據的區間
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//請求部分數據,相應碼是206
if(conn.getResponseCode() == 206){
//流裏此時只有1/3原文件的數據
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
int total = 0;
//拿到臨時文件的輸出流
File file = new File("local_qq.exe");
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//把文件的寫入位置移動至startIndex
raf.seek(startIndex);
while((len = is.read(b)) != -1){
//每次讀取流裏數據之後,同步把數據寫入臨時文件
raf.write(b, 0, len);
total += len;
// System.out.println("線程" + threadId + "下載了" + total);
//生成一個專門用來記錄下載進度的臨時文件
RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
//每次讀取流裏數據之後,同步把當前線程下載的總進度寫入進度臨時文件中
progressRaf.write((total + "").getBytes());
progressRaf.close();
}
System.out.println("線程" + threadId + "下載完畢-------------------");
raf.close();
MultiDownload.finishedThread++;
synchronized (MultiDownload.path) {
if(MultiDownload.finishedThread == MultiDownload.ThreadCount){
for (int i = 0; i < MultiDownload.ThreadCount; i++) {
File f = new File(i + ".txt");
f.delete();
}
MultiDownload.finishedThread = 0;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}