Android學習(54) -- 多線程下載 原理和代碼

多線程下載

原理:服務器CPU分配給每條線程的時間片相同,服務器帶寬平均分配給每條線程,所以客戶端開啓的線程越多,就能搶佔到更多的服務器資源

單線程下載:從輸入流第0個字節開始讀取,讀取到最後一個字節,把讀取到的數據寫到本地文件中,
寫的時候也要從文件的第0個位置開始寫,寫到最後一個位置

多線程的計算:

每個線程預下載的大小:  size = 總的大小/線程的數量  (注意,最後一個問題)
                     size = 10 / 3
id=0線程:  0 -- 2
id=1線程:  3 -- 5
id=2線程:     6 -- 9   (多餘的大小可以給最後一個線程    )
開始位置:   start = 線程編號 * 每個線程預下載的大小 
            start = id * size
結束位置:   end =(id+1)*size -1 
最後一個線程結束位置:總的大小-1 
                    length-1

(使用Java項目進行多線程下載測試,因android比較麻煩,
Java中搞定移植到android中即可,測試下載的時候最好使用可執行程序,
比如.exe程序,這樣可以知道是否可以真成功)

確定每條線程下載多少數據

  • 發送http請求至下載地址

    String path = "http://192.168.1.13:8080/QQ.exe";        
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setReadTimeout(5000);
    conn.setConnectTimeout(5000);
    conn.setRequestMethod("GET");                   
    
  • 獲取文件總長度,然後創建長度一致的臨時文件

    if(conn.getResponseCode() == 200){
        //獲得服務器流中數據的長度
        int length = conn.getContentLength();
        //創建一個臨時文件存儲下載的數據
        RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
        //設置臨時文件的大小
        raf.setLength(length);
        raf.close();
    
  • 確定線程下載多少數據

        //計算每個線程下載多少數據
        int blockSize = length / THREAD_COUNT;
    

計算每條線程下載數據的開始位置和結束位置

    for(int id = 1; id <= 3; id++){
        //計算每個線程下載數據的開始位置和結束位置
        int startIndex = (id - 1) * blockSize;
        int endIndex = id * blockSize - 1;
        if(id == THREAD_COUNT){
            endIndex = length;
        }

        //開啓線程,按照計算出來的開始結束位置開始下載數據
        new DownLoadThread(startIndex, endIndex, id).start();
    }

再次發送請求至下載地址,請求開始位置至結束位置的數據

    String path = "http://192.168.1.13:8080/QQ.exe";

    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setReadTimeout(5000);
    conn.setConnectTimeout(5000);
    conn.setRequestMethod("GET");

    //向服務器請求部分數據
    conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
    conn.connect();

* 下載請求到的數據,存放至臨時文件中

    if(conn.getResponseCode() == 206){
        InputStream is = conn.getInputStream();
        RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
        //指定從哪個位置開始存放數據
        raf.seek(startIndex);
        byte[] b = new byte[1024];
        int len;
        while((len = is.read(b)) != -1){
            raf.write(b, 0, len);
        }
        raf.close();
    }

核心代碼

    public class MultiDownload {

static int ThreadCount = 3;
static int finishedThread = 0;
//確定下載地址
static String path = "http://192.168.1.13:8080/QQ.exe";
public static void main(String[] args) {

    //發送get請求,請求這個地址的資源
    try {
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);

        if(conn.getResponseCode() == 200){
            //拿到所請求資源文件的長度
            int length = conn.getContentLength();

            File file = new File("local_qq.exe");
            //生成臨時文件--佔用磁盤空間--可以防止磁盤空間不夠用
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            //設置臨時文件的大小
            raf.setLength(length);
            raf.close();
            //計算出每個線程應該下載多少字節
            int size = length / ThreadCount;

            for (int i = 0; i < ThreadCount; i++) {
                //計算線程下載的開始位置和結束位置
                int startIndex = i * size;
                int endIndex = (i + 1) * size - 1;
                //如果是最後一個線程,那麼結束位置寫死
                if(i == ThreadCount - 1){
                    endIndex = length - 1;
                }
//                  System.out.println("線程" + i + "的下載區間是:" + startIndex + "---" + endIndex);
                    new DownLoadThread(startIndex, endIndex, i).start();
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


}
class DownLoadThread extends Thread{
    int startIndex; //下載的開始位置
    int endIndex;   //下載的結束位置
    int threadId;   //下載的線程Id

public DownLoadThread(int startIndex, int endIndex, int threadId) {
    super();
    this.startIndex = startIndex;
    this.endIndex = endIndex;
    this.threadId = threadId;
}

@Override
public void run() {
    //再次發送http請求,下載原文件
    try {
        File progressFile = new File(threadId + ".txt");
        //判斷進度臨時文件是否存在
        if(progressFile.exists()){
            FileInputStream fis = new FileInputStream(progressFile);
            BufferedReader br = new BufferedReader(new InputStreamReader(fis));
            //從進度臨時文件中讀取出上一次下載的總進度,然後與原本的開始位置相加,得到新的開始位置
            startIndex += Integer.parseInt(br.readLine());
            fis.close();
        }
        System.out.println("線程" + threadId + "的下載區間是:" + startIndex + "---" + endIndex);
        HttpURLConnection conn;
        URL url = new URL(MultiDownload.path);
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);
        //設置本次http請求所請求的數據的區間
        conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);

        //請求部分數據,相應碼是206
        if(conn.getResponseCode() == 206){
            //流裏此時只有1/3原文件的數據
            InputStream is = conn.getInputStream();
            byte[] b = new byte[1024];
            int len = 0;
            int total = 0;
            //拿到臨時文件的輸出流
            File file = new File("local_qq.exe");
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            //把文件的寫入位置移動至startIndex
            raf.seek(startIndex);
            while((len = is.read(b)) != -1){
                //每次讀取流裏數據之後,同步把數據寫入臨時文件
                raf.write(b, 0, len);
                total += len;
//  System.out.println("線程" + threadId + "下載了" + total);

                //生成一個專門用來記錄下載進度的臨時文件
                RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
                //每次讀取流裏數據之後,同步把當前線程下載的總進度寫入進度臨時文件中
                progressRaf.write((total + "").getBytes());
                progressRaf.close();
            }
            System.out.println("線程" + threadId + "下載完畢-------------------");
            raf.close();

            MultiDownload.finishedThread++;
            synchronized (MultiDownload.path) {
                if(MultiDownload.finishedThread == MultiDownload.ThreadCount){
                    for (int i = 0; i < MultiDownload.ThreadCount; i++) {
                        File f = new File(i + ".txt");
                        f.delete();
                    }
                    MultiDownload.finishedThread = 0;
                }
            }

        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
}
發佈了101 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章