JAVA多線程斷點續傳下載下傳
多個線程同時去下載下傳一個資源,肯定比一個線程去下載下傳一個資源速度快的多,是以現在我們要實作一下多個線程去下載下傳一個資源,而斷點下載下傳的意思是,如果上一次因為某種原因,下載下傳沒有進行到底,再進行下一次下載下傳的時候,可以在上一次下載下傳的位置繼續進行下載下傳,而不需要又從檔案的第一個位元組開始下載下傳。
下面簡單的說一下多線程下載下傳的步驟:
1、通過HttpURLConnection的getContentLength()擷取要下載下傳檔案的總大小
2、根據所開啟的線程的數量,将資源等分,比如這個檔案總大小是6.1M,現在開啟三個線程A、B、C去下載下傳,是以A線程下載下傳的範圍是0~2M,B線程下載下傳的範圍是2~4M,C線程下載下傳的範圍是4~6.1M,我們可以讓最後一個線程辛苦一點,多下0.1M。
3、在本地建立與線程數量對應的臨時檔案(positionFile)用于記錄每個線程下載下傳的位置,這樣下次就可以從這個位置繼續下載下傳
4、如果每個線程全部下載下傳完畢,則可以删除臨時檔案。
好了,下面是代碼實作。
我把一個叫StringBuffer的視訊放在tomcat伺服器上,然後開啟了4個線程去同時下載下傳這個視訊。
public class MutileThreaddownload {
// 線程的數量
private static int threadCount = 4;
// 每個下載下傳區塊的大小
private static long blockSize;
// 正在運作的線程的數量
private static int runningThreadCount;
public static void main(String[] args) {
try {
String downloadPath="http://localhost:8080/FormWorkTest/StringBuffer.avi";
URL downloadUrl=new URL(downloadPath);
HttpURLConnection coon=(HttpURLConnection) downloadUrl.openConnection();
coon.setRequestMethod("GET");
coon.setReadTimeout(5000);
int code = coon.getResponseCode();
if(code==200){
runningThreadCount = threadCount;
//獲得伺服器端目标檔案的長度
int fileLength = coon.getContentLength();
System.out.println("伺服器端檔案的大小是:"+fileLength);
//1、在本地弄一個和目标檔案一樣大的file對象
File descFile=new File("temp.avi");
RandomAccessFile raf=new RandomAccessFile(descFile, "rw");
raf.setLength(fileLength);
//2、計算出每一個線程應該下載下傳檔案的大小
blockSize=fileLength/threadCount;
//3、開啟若幹個線程去下載下傳檔案
for (int i = 0; i < threadCount; i++) {
//先設定每個線程下載下傳的開始位置和結束位置
long startIndex=i*blockSize;
long endIndex=(i+1)*blockSize-1;
//如果是最後一個線程
if(i==threadCount-1){
endIndex=fileLength-1;
}
new <span style="color:#ff6666;">DownLoadThread</span>(downloadPath,startIndex,endIndex,i,runningThreadCount,threadCount).start();
}
raf.close();
}
coon.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class <span style="color:#ff6666;">DownLoadThread </span>extends Thread {
private long endIndex;
private long startIndex;
private String urlPath;
private int i;
private static int runningThreadCount;
private int threadCount;
DownLoadThread(String urlPath,long startIndex,long endIndex ,int i,int runningThreadCount,int threadCount){
this.i=i;
this.endIndex=endIndex;
this.startIndex=startIndex;
this.urlPath=urlPath;
this.runningThreadCount=runningThreadCount;
this.threadCount=threadCount;
}
@Override
public void run() {
try {
long totalSize=0,len=0;
//建立positionFile,用于存儲每個線程下載下傳的檔案位元組位置
File positionFile=new File(String.valueOf(i)+".txt");
// 接着從上一次的位置繼續下載下傳資料
if (positionFile.exists() && positionFile.length() > 0) {// 判斷是否有記錄
FileInputStream fis = new FileInputStream(positionFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
// 擷取目前線程上次下載下傳的總大小是多少
String lasttotalstr = br.readLine();
int lastTotal = Integer.valueOf(lasttotalstr);
System.out.println("上次線程" + i + "下載下傳的總大小:"+ lastTotal);
startIndex += lastTotal;
totalSize += lastTotal;// 加上上次下載下傳的總大小。
fis.close();
}
URL downLoadUrl=new URL(urlPath);
HttpURLConnection connection = (HttpURLConnection) downLoadUrl.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setRequestProperty("Range", "bytes=" + startIndex + "-"+ endIndex);
int code = connection.getResponseCode();
System.out.println("這是第"+i+"個線程,"+"code=" + code);
//下載下傳檔案的一部分的傳回碼是206
if(code==206){
InputStream inputStream = connection.getInputStream();
BufferedInputStream bis=new BufferedInputStream(inputStream);
File tempFile=new File("temp.avi");
RandomAccessFile raf=new RandomAccessFile(tempFile, "rw");
//指定檔案開始讀取的位置
raf.seek(startIndex);
System.out.println("這是第"+i+"個線程,下載下傳範圍是"+startIndex+"~"+endIndex);
byte[] buffer=new byte[1024];
while((len=bis.read(buffer))!=-1){
//這裡的模式必須是"rwd"
RandomAccessFile positionRaf=new RandomAccessFile(positionFile, "rwd");
totalSize+=len;
raf.write(buffer, 0, buffer.length);
positionRaf.write(String.valueOf(totalSize).getBytes());
positionRaf.close();
}
bis.close();
raf.close();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 隻有所有的線程都下載下傳完畢後 才可以删除記錄檔案。
synchronized (DownLoadThread.class) {
System.out.println("線程" + i + "下載下傳完畢了");
--runningThreadCount;
System.out.println(runningThreadCount);
if (runningThreadCount < 1) {
System.out.println("所有的線程都工作完畢了。删除臨時記錄的檔案");
for (int i = 0; i < threadCount; i++) {
File f = new File(i + ".txt");
System.out.println(f.delete());
}
}
}
}
}
}
測試結果:
正常情況下的結果如下圖
如果在下載下傳的過程中,你突然中斷的JVM,下次再次下載下傳的時候,控制台輸出的結果如下圖: