中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

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

發布時間:2020-08-01 08:49:20 來源:網絡 閱讀:10475 作者:andywang66 欄目:移動開發

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

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

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

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

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

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

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

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

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

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

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

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

5: 使用for循環開啟3個子線程

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

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

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

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

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

此處有個注意事項:讓RandomAccessFile對象寫字節流之前,需要移動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. 確定開啟幾個線程
     *開啟線程的總數=CPU核數+1;例如:CPU核數為4,則最多可開啟5條線程
     */
    int availableProcessors = Runtime.getRuntime().availableProcessors();
    System.out.println("CPU核數是:"+availableProcessors);
    int threadCount = 3;

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

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

            } else {
                System.out.println("下載失敗,響應碼是:"+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 ;
    }
}

示例代碼運行結果如下:

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


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

向AI問一下細節

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

AI

兰考县| 兖州市| 浦东新区| 库车县| 永吉县| 北海市| 当阳市| 乐平市| 揭西县| 闽侯县| 海南省| 高平市| 汤阴县| 丰原市| 浙江省| 大埔区| 新民市| 桦川县| 茶陵县| 宝应县| 贵溪市| 呼伦贝尔市| 虞城县| 内丘县| 宜章县| 剑河县| 上杭县| 海门市| 普兰店市| 晴隆县| 石楼县| 微山县| 乌审旗| 阿克苏市| 潞城市| 竹溪县| 宣恩县| 潼南县| 察哈| 鹤岗市| 公主岭市|