1. 程式人生 > >Java多執行緒下載原理與實現

Java多執行緒下載原理與實現

多執行緒下載原理

  • 客戶端要下載一個檔案, 首先請求伺服器,伺服器將這個檔案傳送給客戶端,客戶端儲存到本地, 完成了一個下載的過程.
  • 多執行緒下載的思想是客戶端開啟多個執行緒同時下載,每個執行緒只負責下載檔案的一部分, 當所有執行緒下載完成的時候,檔案下載完畢.
    • 並不是執行緒越多下載越快, 與網路環境有很大的關係
    • 在同等的網路環境下,多執行緒下載速度要高於單執行緒.
    • 多執行緒下載佔用資源比單執行緒多,相當於用資源換取速度

java程式碼實現多執行緒下載

程式碼的思路:

  1. 首先要獲取要下載檔案的大小
  2. 在磁碟上使用RandomAccessFile 這個類在磁碟上建立一個大小一樣的檔案,將來將資料寫入這個檔案.
  3. 為每個執行緒分配下載任務. 內容包括執行緒現在檔案的開始位置和結束位置.這裡面有一點數學知識,程式碼中有備註.
  4. 啟動下載執行緒
  5. 判斷有沒有儲存上次下載的臨時檔案.
  6. 在啟動執行緒下載的時候儲存下載的位置資訊
  7. 下載完畢後刪除當前執行緒產生的臨時檔案
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(); } } }