1. 程式人生 > >用java實現檔案的斷點續傳併發下載

用java實現檔案的斷點續傳併發下載

說明

用java實現檔案的斷點續傳,使用了HTTP的首部欄位實現,在網上看到例子,手動實現一遍,理解其原理,在這記錄下

正文

要實現斷點續傳,要在請求中設定請求開始的位置和結束位置,在HTTP請求中設定RANGE首部欄位,之後伺服器如果能正常返回,返回206狀態碼
用java實現的關鍵點:
1.設定請求的首部欄位,使用java的net包
2.在讀取資原始檔後,要儲存檔案,從斷點處儲存,使用RandAccessFile類
3.使用多執行緒併發的方式進行,如何正確設定起始位置

主要思路就是:
1. 設定檔案資訊,包括檔案所在的URL,檔名,檔案儲存的路徑及檔案需要分段下載的次數
2. 下載時,先連線伺服器,得到檔案的大小,通過伺服器響應的首部欄位Content-Length獲得,得到檔案大小後,根據分段下載的次數設定每次開始的位置,結束位置。並創造一個資訊臨時檔案,用來儲存每次分段下載的起始位置,用於非第一次下載時,可以直接本地讀取起始資訊
3. 分段下載根據開始位置,儲存在下載檔案的合適位置,使用RandAccessFile類的seek()方法定位
4.

1、設定首部欄位

在分段抓取的類中,根據分段次數,設定開始位置

URL ourl = new URL(url);
HttpURLConnection httpConnection = (HttpURLConnection) ourl.openConnection();
String prop = "bytes=" + startPos + "-";
httpConnection.setRequestProperty("RANGE", prop); //設定請求首部欄位 RANGE

分段抓取程式碼

package action;

import java.io.IOException;
import
java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import util.FileUtil; import util.LogUtil; /** * 用於分段傳輸 * 使用HTTP協議的首部欄位實現 * @author wds * */ public class FileSplitFetch implements Runnable{ protected String url; // 檔案所在url
protected long startPos; // 分段傳輸的開始位置 protected long endPos; // 結束位置 protected int threadID; // 執行緒編號 protected boolean downOver = false; // 下載完成標誌 protected boolean stop = false; // 當前分段結束標誌 FileUtil fileUtil = null; // 檔案工具 public FileSplitFetch(String url, long startPos, long endPos, int threadID, String fileName) throws IOException { super(); this.url = url; this.startPos = startPos; this.endPos = endPos; this.threadID = threadID; fileUtil = new FileUtil(fileName, startPos); } @Override public void run() { while(startPos < endPos && !stop){ try { URL ourl = new URL(url); HttpURLConnection httpConnection = (HttpURLConnection) ourl.openConnection(); String prop = "bytes=" + startPos + "-"; httpConnection.setRequestProperty("RANGE", prop); //設定請求首部欄位 RANGE LogUtil.log(prop); InputStream input = httpConnection.getInputStream(); byte[] b = new byte[1024]; int bytes = 0; while((bytes = input.read(b)) > 0 && startPos < endPos && !stop){ startPos += fileUtil.write(b, 0, bytes); } LogUtil.log("Thread" + threadID + " is done"); downOver = true; } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 列印響應的頭部資訊 * @param conn */ public void printResponseHeader(HttpURLConnection conn){ for(int i = 0; ; i++){ String fieldsName = conn.getHeaderFieldKey(i); if(fieldsName != null){ LogUtil.log(fieldsName + ":" + conn.getHeaderField(fieldsName)); }else{ break; } } } /** * 停止分段傳輸 */ public void setSplitTransStop(){ stop = true; } }

2、檔案的儲存

在檔案儲存時,要根據開始位置儲存在下載檔案的適當位置,使用RandomAccessFile的seek()方法

public class FileUtil{

    private RandomAccessFile file; 
    private long startPos; // 檔案儲存的起始位置

    public FileUtil(String fileName, long startPos) throws IOException{
        file = new RandomAccessFile(fileName, "rw");
        this.startPos = startPos;
        file.seek(startPos);
    }

    public synchronized int write(byte[] data, int start, int len){
        int res = -1;
        try {
            file.write(data, start, len);
            res = len;
        } catch (IOException e) {
            LogUtil.log(e.getMessage());
            e.printStackTrace();
        }
        return res;
    }

}

3、併發下載

在下載檔案時,可以使用多個子執行緒併發進行,這裡使用了一個數組儲存多個執行緒

/**
     * 開始下載檔案
     * 1. 獲取檔案長度
     * 2. 分割檔案
     * 3. 例項化分段下載子執行緒
     * 4. 啟動子執行緒
     * 5. 等待子執行緒的返回
     * @throws IOException 
     */
    public void startDown(){

        if(firstDown){
            fileLen = getFileSize();
            if(fileLen == -1){
                LogUtil.log("檔案大小未知");
                return;
            }else if(fileLen == -2){
                LogUtil.log("檔案不可訪問");
                return;
            }
            else{

                // 設定每次分段下載的開始位置
                for(int i = 0; i < startPos.length; i++){
                    startPos[i] = i * (fileLen / startPos.length);
                }

                //設定每次分段下載的結束位置
                for(int i = 0; i < endPos.length - 1; i++){
                    endPos[i] = startPos[i + 1];
                }
                endPos[endPos.length - 1] = fileLen;

            }

        }

        //啟動分段下載子執行緒

        try {
                fileSplitFetchs = new FileSplitFetch[startPos.length];
                for(int i = 0; i < startPos.length; i++){
                    System.out.println(startPos[i] + " " + endPos[i]);
                    fileSplitFetchs[i] = new FileSplitFetch(siteInfo.getUrl(), startPos[i], endPos[i], i, 
                            siteInfo.getFilePath() + File.separator + siteInfo.getFileName());
                    LogUtil.log("Threa " + i + ", start= " + startPos[i] + ",  end= " + endPos[i]);
                    new Thread(fileSplitFetchs[i]).start();
                }

                //儲存檔案下載資訊
                saveInfo();
                //迴圈判斷所有檔案是否下載完畢
                boolean breakWhile = false;
                while(!stop){

                    LogUtil.sleep(500);
                    breakWhile = true;

                    for(int i = 0; i < startPos.length; i++){
                        if(! fileSplitFetchs[i].downOver){
                            breakWhile = false; // 還存在未下載完成的執行緒
                            break;
                        }
                    }

                    if(breakWhile)
                        break;
                }
        } catch (IOException e) {
            LogUtil.log(e.getMessage());
            e.printStackTrace();
        }

        LogUtil.log("檔案下載完成");
    }

4、設定檔案的基本資訊

基本資訊包含了檔案所在站點資訊,檔案本地儲存路徑,檔名,檔案分段下載次數

public class SiteInfo implements Serializable {

    private static final int SPLIT_COUNT = 5; // 預設次數為5次

    private String url;        // 檔案所在站點的url
    private String filePath;   // 檔案儲存的路徑
    private String fileName;   // 檔案的名字
    private int splits;        // 分段下載檔案的次數

    public SiteInfo(){
        this("","","",SPLIT_COUNT);
    }

    public SiteInfo(String url, String filePath, String fileName, int splits) {
        this.url = url;
        this.filePath = filePath;
        this.fileName = fileName;
        this.splits = splits;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public int getSplits() {
        return splits;
    }

    public void setSplits(int splits) {
        this.splits = splits;
    }

    public String getSimpleName(){
        String[] names = fileName.split("\\.");
        return names[0];
    }



}