Java多執行緒下載原理與實現
阿新 • • 發佈:2018-11-19
多執行緒下載原理
- 客戶端要下載一個檔案, 首先請求伺服器,伺服器將這個檔案傳送給客戶端,客戶端儲存到本地, 完成了一個下載的過程.
- 多執行緒下載的思想是客戶端開啟多個執行緒同時下載,每個執行緒只負責下載檔案的一部分, 當所有執行緒下載完成的時候,檔案下載完畢.
- 並不是執行緒越多下載越快, 與網路環境有很大的關係
- 在同等的網路環境下,多執行緒下載速度要高於單執行緒.
- 多執行緒下載佔用資源比單執行緒多,相當於用資源換取速度
java程式碼實現多執行緒下載
程式碼的思路:
- 首先要獲取要下載檔案的大小
- 在磁碟上使用RandomAccessFile 這個類在磁碟上建立一個大小一樣的檔案,將來將資料寫入這個檔案.
- 為每個執行緒分配下載任務. 內容包括執行緒現在檔案的開始位置和結束位置.這裡面有一點數學知識,程式碼中有備註.
- 啟動下載執行緒
- 判斷有沒有儲存上次下載的臨時檔案.
- 在啟動執行緒下載的時候儲存下載的位置資訊
- 下載完畢後刪除當前執行緒產生的臨時檔案
package com.yb.muchthreaddown;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 多執行緒下載 和 斷點續傳
* @author.liu
*
*/
public class MuchThreadDown {
// private String path = "http://mpge.5nd.com/2016/2016-11-15/74847/1.mp3"; //下載路徑
private String path = "http://117.169.69.238/mp3.9ku.com/m4a/186947.m4a";
private String targetFilePath="/" ; //下載檔案存放目錄
private int threadCount = 3; //執行緒數量
/**
* 構造方法
* @param path 要下載檔案的網路路徑
* @param targetFilePath 儲存下載檔案的目錄
* @param threadCount 開啟的執行緒數量,預設為 3
*/
public MuchThreadDown(String path, String targetFilePath, int threadCount) {
this.path = path;
this.targetFilePath = targetFilePath;
this.threadCount = threadCount;
}
/**
* 下載檔案
*/
public void download() throws Exception{
//連線資源
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
int code = connection.getResponseCode();
if(code == 200){
//獲取資源大小
int connectionLength = connection.getContentLength();
System.out.println(connectionLength);
//在本地建立一個與資源同樣大小的檔案來佔位
RandomAccessFile randomAccessFile = new RandomAccessFile(new File(targetFilePath,getFileName(url)), "rw");
randomAccessFile.setLength(connectionLength);
/*
* 將下載任務分配給每個執行緒
*/
int blockSize = connectionLength/threadCount;//計算每個執行緒理論上下載的數量.
for(int threadId = 0; threadId < threadCount; threadId++){//為每個執行緒分配任務
int startIndex = threadId * blockSize; //執行緒開始下載的位置
int endIndex = (threadId+1) * blockSize -1; //執行緒結束下載的位置
if(threadId == (threadCount - 1)){ //如果是最後一個執行緒,將剩下的檔案全部交給這個執行緒完成
endIndex = connectionLength - 1;
}
new DownloadThread(threadId, startIndex, endIndex).start();//開啟執行緒下載
}
// randomAccessFile.close();
}
}
//下載的執行緒
private class DownloadThread extends Thread{
private int threadId;
private int startIndex;
private int endIndex;
public DownloadThread(int threadId, int startIndex, int endIndex) {
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
System.out.println("執行緒"+ threadId + "開始下載");
try {
//分段請求網路連線,分段將檔案儲存到本地.
URL url = new URL(path);
//載入下載位置的檔案
File downThreadFile = new File(targetFilePath,"downThread_" + threadId+".dt");
RandomAccessFile downThreadStream = null;
if(downThreadFile.exists()){//如果檔案存在
downThreadStream = new RandomAccessFile(downThreadFile,"rwd");
String startIndex_str = downThreadStream.readLine();
if(null==startIndex_str||"".equals(startIndex_str)){ //網友 imonHu 2017/5/22
this.startIndex=startIndex;
}else{
this.startIndex = Integer.parseInt(startIndex_str)-1;//設定下載起點
}
}else{
downThreadStream = new RandomAccessFile(downThreadFile,"rwd");
}
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
//設定分段下載的頭資訊。 Range:做分段資料請求用的。格式: Range bytes=0-1024 或者 bytes:0-1024
connection.setRequestProperty("Range", "bytes="+ startIndex + "-" + endIndex);
System.out.println("執行緒_"+threadId + "的下載起點是 " + startIndex + " 下載終點是: " + endIndex);
if(connection.getResponseCode() == 206){//200:請求全部資源成功, 206代表部分資源請求成功
InputStream inputStream = connection.getInputStream();//獲取流
RandomAccessFile randomAccessFile = new RandomAccessFile(
new File(targetFilePath,getFileName(url)), "rw");//獲取前面已建立的檔案.
randomAccessFile.seek(startIndex);//檔案寫入的開始位置.
/*
* 將網路流中的檔案寫入本地
*/
byte[] buffer = new byte[1024];
int length = -1;
int total = 0;//記錄本次下載檔案的大小
while((length = inputStream.read(buffer)) > 0){
randomAccessFile.write(buffer, 0, length);
total += length;
/*
* 將當前現在到的位置儲存到檔案中
*/
downThreadStream.seek(0);
downThreadStream.write((startIndex + total + "").getBytes("UTF-8"));
}
downThreadStream.close();
inputStream.close();
randomAccessFile.close();
cleanTemp(downThreadFile);//刪除臨時檔案
System.out.println("執行緒"+ threadId + "下載完畢");
}else{
System.out.println("響應碼是" +connection.getResponseCode() + ". 伺服器不支援多執行緒下載");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//刪除執行緒產生的臨時檔案
private synchronized void cleanTemp(File file){
file.delete();
}
//獲取下載檔案的名稱
private String getFileName(URL url){
String filename = url.getFile();
return filename.substring(filename.lastIndexOf("/")+1);
}
public static void main(String[] args) {
try {
new MuchThreadDown(null, null, 3).download();
} catch (Exception e) {
e.printStackTrace();
}
}
}