溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

多線程下載的基本原理和用法

發(fā)布時間:2020-08-01 08:49:20 來源:網(wǎng)絡(luò) 閱讀:10465 作者:andywang66 欄目:移動開發(fā)

剛學了下多線程的下載,可能是初次接觸的原因吧,理解起來覺得稍微有點難。所以想寫一篇博客來記錄下,加深自己理解的同時,也希望能夠幫到一些剛接觸的小伙伴。由于涉及到網(wǎng)絡(luò)的傳輸,那么就會涉及到http協(xié)議。建議在讀本文之前您對http協(xié)議有一定的了解。

線程可以通俗的理解為下載的通道,一個線程就是文件下載的一個通道,多線程就是同時打開了多個通道對文件進行下載。當服務(wù)器提供下載服務(wù)時,用戶之間共享帶寬,在優(yōu)先級相同的情況下,總服務(wù)器會對總下載線程進行平均分配。我們平時用的迅雷下載就是多線程下載。

多線程的下載大致可以分為如下幾個步驟:

1: 獲取目標文件的大小(totalSize)

按照常識,我們在下載一個文件之前,通常情況下是要知道該文件的大小,這樣才好在本地留好足量的空間來存儲,免得出現(xiàn)還未下載完,存儲空間就爆了的情況。為了方便代碼的演示,本文在本地tomcat服務(wù)器的webapps/ROOT目錄下新建一個test.txt的文件,里面存儲了0123456789這10字節(jié)的數(shù)據(jù)。

2: 確定要開啟幾個線程(threadCount)

需要的文件在服務(wù)器上,那我們要開通幾個通道去下載呢?一般情況下這是由CPU去決定的,但是CPU開啟線程的數(shù)目也是有限的,不是想開幾個線程就開幾個線程。所開線程的最大數(shù)量=(CPU核數(shù)+1),例如你的CPU核數(shù)為4,那么電腦最多可以開啟5條線程。為了方便代碼演示,本文的threadCount=3

3: 計算平均每個線程需要下載多少個字節(jié)的數(shù)據(jù)(blockSize)

理想情況下多線程下載是按照平均分配原則的,即:單線程下載的字節(jié)數(shù)等于文件總大小除以開啟的線程總條數(shù),當不能整除時,則最后開啟的線程將剩余的字節(jié)一起下載。例如:本文中的totalSize=10,threadCount=3,則前兩個開啟的線程下載3KB的數(shù)據(jù),第三個開啟的線程需要下載(3+1)KB的數(shù)據(jù)。

4:計算各個線程要下載的字節(jié)范圍。

平時我們做項目講究分工明確,同理多線程下載也需要明確各個下載的字節(jié)范圍,這樣才能將文件高效、快速、準確的下載下來。即在下載過程中,各個線程都要明確自己的開始索引(startIndex)和結(jié)束索引(endIndex)。

多線程下載的基本原理和用法
從上圖我們可以總結(jié)出一個公式:
startIndex = threadId乘以blockSize;
endIndex = (threadId+1)乘以blockSize-1;
如果是最后一條線程,那么結(jié)束索引為:
endIndex = totalSize - 1;

5: 使用for循環(huán)開啟3個子線程

    //每次循環(huán)啟動一條線程下載
    for(int threadId=0; threadId<3;threadId++){
        /**
         * 計算各個線程要下載的字節(jié)范圍
         */
        //開始索引
        int startIndex = threadId * blockSize;
        //結(jié)束索引
        int endIndex = (threadId+1)* blockSize-1;
        //如果是最后一條線程(因為最后一條線程可能會長一點)
        if(threadId == (threadCount -1)){
            endIndex = totalSize -1;
        }
        /**
         * 啟動子線程下載
         */
        new DownloadThread(threadId,startIndex,endIndex).start();
    }

6:獲取各個線程的目標文件的開始索引和結(jié)束索引的范圍。

告訴服務(wù)器,只要目標段的數(shù)據(jù),這樣就需要通過Http協(xié)議的請求頭去設(shè)置(range:bytes=0-499 )

connection.setRequestProperty("range", "bytes="+startIndex+"-"+endIndex);

7:使用RandomAccessFile隨機文件訪問類。創(chuàng)建一個RandomAccessFile對象,將返回的字節(jié)流寫到文件指定的范圍

此處有個注意事項:讓RandomAccessFile對象寫字節(jié)流之前,需要移動RandomAccessFile對象到指定的位置開始寫。

    raf.seek(startIndex);

以上就是多線程下載的大致步驟。代碼如下:

package com.example;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class DownloadTest {
private static final String path = "http://localhost:8080/test.txt";
public static void main(String[] args) throws Exception {
    /**
     * 1.獲取目標文件的大小
     */
    int totalSize = new URL(path).openConnection().getContentLength();
    System.out.println("目標文件的總大小為:"+totalSize+"B");
    /**
     *2. 確定開啟幾個線程
     *開啟線程的總數(shù)=CPU核數(shù)+1;例如:CPU核數(shù)為4,則最多可開啟5條線程
     */
    int availableProcessors = Runtime.getRuntime().availableProcessors();
    System.out.println("CPU核數(shù)是:"+availableProcessors);
    int threadCount = 3;

    /**
     * 3. 計算每個線程要下載多少個字節(jié)
     */
    int blockSize = totalSize/threadCount;

    //每次循環(huán)啟動一條線程下載
    for(int threadId=0; threadId<3;threadId++){
        /**
         * 4.計算各個線程要下載的字節(jié)范圍
         */
        //開始索引
        int startIndex = threadId * blockSize;
        //結(jié)束索引
        int endIndex = (threadId+1)* blockSize-1;
        //如果是最后一條線程(因為最后一條線程可能會長一點)
        if(threadId == (threadCount -1)){
            endIndex = totalSize -1;
        }
        /**
         * 5.啟動子線程下載
         */
        new DownloadThread(threadId,startIndex,endIndex).start();
    }
}

//下載的線程類
private static class DownloadThread extends Thread{
    private int threadId;
    private int startIndex;
    private int endIndex;
    public DownloadThread(int threadId, int startIndex, int endIndex) {
        super();
        this.threadId = threadId;
        this.startIndex = startIndex;
        this.endIndex = endIndex;
    }

    @Override
    public void run(){
        System.out.println("第"+threadId+"條線程,下載索引:"+startIndex+"~"+endIndex);
        //每條線程要去×××器拿取目標段的數(shù)據(jù)
        try {
            //創(chuàng)建一個URL對象
            URL url = new URL(path);
            //開啟網(wǎng)絡(luò)連接
            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
            //添加配置
            connection.setConnectTimeout(5000);
            /**
             * 6.獲取目標文件的[startIndex,endIndex]范圍
             */
            //告訴服務(wù)器,只要目標段的數(shù)據(jù),這樣就需要通過Http協(xié)議的請求頭去設(shè)置(range:bytes=0-499 )
            connection.setRequestProperty("range", "bytes="+startIndex+"-"+endIndex);
            connection.connect();
            //獲取響應(yīng)碼,注意,由于服務(wù)器返回的是文件的一部分,因此響應(yīng)碼不是200,而是206
            int responseCode = connection.getResponseCode();
            //判斷響應(yīng)碼的值是否為206
            if (responseCode == 206) {
                //拿到目標段的數(shù)據(jù)
                InputStream is = connection.getInputStream();
                /**
                 * 7:創(chuàng)建一個RandomAccessFile對象,將返回的字節(jié)流寫到文件指定的范圍
                 */
                //獲取文件的信息
                String fileName = getFileName(path);
                //rw:表示創(chuàng)建的文件即可讀也可寫。
                RandomAccessFile raf = new RandomAccessFile("d:/"+fileName, "rw");
                /**
                 * 注意:讓raf寫字節(jié)流之前,需要移動raf到指定的位置開始寫
                 */
                raf.seek(startIndex);
                //將字節(jié)流數(shù)據(jù)寫到file文件中
                byte[] buffer = new byte[1024];
                int len = 0;
                while((len=is.read(buffer))!=-1){
                    raf.write(buffer, 0, len);
                }
                //關(guān)閉資源
                is.close();
                raf.close();
                System.out.println("第 "+ threadId +"條線程下載完成 !");

            } else {
                System.out.println("下載失敗,響應(yīng)碼是:"+responseCode);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//獲取文件的名稱
private static String getFileName(String path){
    //http://localhost:8080/test.txt
    int index = path.lastIndexOf("/");
    String fileName = path.substring(index+1);
    return fileName ;
    }
}

示例代碼運行結(jié)果如下:

目標文件的總大小為:10B
CPU核數(shù)是:4
第0個線程,下載索引:0~2
第1個線程,下載索引:3~5
第2個線程,下載索引:6~9
第1個線程下載完成!
第2個線程下載完成!
第0個線程下載完成!


好了,本文寫到此為止。以上是我個人對多線程下載的初步理解,如有不妥之處,還望大家多多指點,感謝!讓我們共同學習,一起進步。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI