天天看點

JAVA多線程斷點續傳下載下傳

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());
					}
				}
			}
		}
	}
}
           

測試結果:

正常情況下的結果如下圖

JAVA多線程斷點續傳下載下傳
JAVA多線程斷點續傳下載下傳

如果在下載下傳的過程中,你突然中斷的JVM,下次再次下載下傳的時候,控制台輸出的結果如下圖:

JAVA多線程斷點續傳下載下傳