多線程斷點續傳下載

使用多線程下載文件可以更快完成文件的下載,多線程下載文件之所以快,是因爲其搶佔的服務器資源多。如:假設服務器同時最多服務100個用戶,在服務器中一條線程對應一個用戶,100條線程在計算機中並非併發執行,而是由CPU劃分時間片輪流執行,如果A應用使用了99條線程下載文件,那麼相當於佔用了99個用戶的資源,假設一秒內CPU分配給每條線程的平均執行時間是10ms,A應用在服務器中一秒內就得到了990ms的執行時間,而其他應用在一秒內只有10ms的執行時間。就如同一個水龍頭,每秒出水量相等的情況下,放990毫秒的水
肯定比放10毫秒的水要多。
多線程下載的實現過程:
 
簡單瞭解一下多條線程將一條數據流分段下載的小思路:
假設10byte的數據流分3條線程下載:
10/3+1=4,即每條線程下載4個字節,0~3,4~7,8~9三段線程數據流(第三段理論值是4個字節,但由於下載2個字節就結束數據下載內容了,所以數據流會自動關閉),那麼如下計算各個線程應該從什麼位置開始下載與在哪個位置結束呢?
每條線程下載的數據長度:
10%3==0? 10/3  :10/3+1 即數據總長度%線程條數
計算線程開始位置公式:
 
線程的id(即第幾條線程,起始值爲0)* 每條線程下載的數據長度 = ?
計算線程結束位置公式:
 
線程的id(即第幾條線程,起始值爲0)+1 * 每條線程下載的數據長度-1 = ?

1>首先得到下載文件的長度,然後設置本地文件
的長度。
HttpURLConnection.getContentLength();
RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");
file.setLength(filesize);//設置本地文件的長度

2>根據文件長度和線程數計算每條線程下載的數據長度和下載位置。如:文件的長度爲6M,線程數爲3,那麼,每條線程下載的數據長度爲2M,每條線程開始下載的位置如上圖所示。
3>使用Http的Range頭字段指定每條線程從文件的什麼位置開始下載,下載到什麼位置爲止,如:指定從文件的2M位置開始下載,下載到位置(4M-1byte)爲止,代碼如下:
HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");

4>保存文件,使用RandomAccessFile類指定每條線程從本地文件的什麼位置開始寫入數據。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");
threadfile.seek(2097152);//從文件的什麼位置開始寫入數據
 
核心代碼
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class ThreadDowmLoad {

  public static void main(String[] args) {
    String path = "http://file1.51cto.com/index.php?aid=785088&k=205dfb403540e1ee182fd48c80247ab6&t=1347439680";
    int threadNum = 3;
    try {
      new ThreadDowmLoad().downLoad(path, threadNum);
    } catch (Exception e) {

      e.printStackTrace();
    }

  }

  /**
    * 從路徑中獲取下載文件名稱
    *    
    * @param path下載文件的路徑
    * @return 最後一個"/"後面字段的內容作爲下載後的文件名
    */

  public static String getFileName(String path) {
    // 獲得最後一個"/"後面字段的內容作爲下載後的文件名
    return path.substring(path.lastIndexOf("/") + 1);
  }

  /**
    * @param path下載文件的路徑
    * @param threadNum線程數
    * @throws Exception
    */

  public void downLoad(String path, int threadNum) throws Exception {
    URL url = new URL(path);
    HttpURLConnection httpURLConnection = (HttpURLConnection) url
        .openConnection();
    // 得到httpURLConnection對象進行相關設置
    // 設置請求方式
    httpURLConnection.setRequestMethod("GET");
    // 設置鏈接超時時間
    httpURLConnection.setConnectTimeout(5 * 1000);
    // 通過getContentLength()取得下載長度
    int downloadLength = httpURLConnection.getContentLength();
    // 生成同長度的本地文件,存放下載數據-----RandomAccessFile隨機文件訪問類
    String fileName = getFileName(path);// 從路徑中獲取下載文件名稱
    // 通過相對路徑將下載後的文件保存到根目錄中
    File saveFile = new File(fileName);
    // "rwd"表示同步將文件下載到指定路徑中,沒有緩存,若沒有“d”則會有緩存不同步
    RandomAccessFile randomAccessFile = new RandomAccessFile(saveFile,
        "rwd");
    // 保存在本地的文件長度=網絡下載的文件長度
    randomAccessFile.setLength(downloadLength);
    randomAccessFile.close();

    // 計算每條線程下載的數據長度
    int block = downloadLength % threadNum == 0 ? (downloadLength / threadNum)
        : (downloadLength / threadNum) + 1;
    // 計算每條線程開始下載與結束下載的位置
    for (int i = 0; i < threadNum; i++) {
      // 開線程下載各自的數據段
      new DownLoadThread(url, saveFile, block, threadNum).start();

    }
  }

  /**
    *    
    * @author YanJun URL url, 路徑 File saveFile,文件名 int block, 數據段長度 int
    *                 threadNum 指定線程
    */

  private final class DownLoadThread extends Thread {
    private URL url;
    private File saveFile;
    private int block;
    private int threadNum;

    public DownLoadThread(URL url, File saveFile, int block, int threadNum) {
      super();
      this.url = url;
      this.saveFile = saveFile;
      this.block = block;
      this.threadNum = threadNum;
    }

    @Override
    public void run() {
      // 計算下載文件數據的開始和結束位置
      int startPosition = threadNum * block;
      int endPosition = (threadNum + 1) * block - 1;

      try {
        // 隨機文件訪問類
        RandomAccessFile randomAccessFile = new RandomAccessFile(
            saveFile, "rwd");
        // 從什麼位置訪問數據randomAccessFile.seek(startPosition);
        randomAccessFile.seek(startPosition);
        HttpURLConnection httpURLConnection = (HttpURLConnection) url
            .openConnection();
        // 得到httpURLConnection對象進行相關設置
        // 設置請求方式
        httpURLConnection.setRequestMethod("GET");
        // 設置鏈接超時時間
        httpURLConnection.setConnectTimeout(5 * 1000);
        // 使用Http的Range頭字段指定每條線程從文件的什麼位置開始下載,下載到什麼位置爲止,如:指定從文件的2M位置開始下載
        httpURLConnection.setRequestProperty("Range", "bytes="
            + startPosition + "-" + endPosition);
        // 取得輸入流
        InputStream inputStream = httpURLConnection.getInputStream();
        byte[] buffer = new byte[1024];
        int len = 0;// 當len=-1,表示流讀取完畢
        // inputStream.read(buffer)--從輸入流裏讀取數據到緩衝區
        while ((len = inputStream.read(buffer)) != -1) {
          randomAccessFile.write(buffer, 0, len);

        }
        inputStream.close();
        randomAccessFile.close();
        System.out.println(threadNum + "號下載完成");
      } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }

  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章