日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

通过HTTP协议实现多线程下载

發(fā)布時(shí)間:2024/4/17 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 通过HTTP协议实现多线程下载 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 基本原理,每條線程從文件不同的位置開始下載,最后合并出完整的數(shù)據(jù)。?


2. 使用多線程下載的好處?
??? 下載速度快。為什么呢?很好理解,以往我是一條線程在服務(wù)器上下載。也就是說(shuō),對(duì)應(yīng)在服務(wù)器上,有一個(gè)我的下載線程存在。?
??? 這時(shí)候肯定不只我一個(gè)人在下載,服務(wù)器上肯定同時(shí)存在多條下載線程,在下載服務(wù)器資源。對(duì)于 CPU 來(lái)說(shuō),不可能實(shí)現(xiàn)并發(fā)執(zhí)行。?
??? CPU 會(huì)公平的為這些線程劃分時(shí)間片,輪流執(zhí)行,a線程十毫秒 , b線程十毫秒...?
??? 假設(shè)運(yùn)用了本文這種手法,意味著我的下載應(yīng)用,可以同時(shí)使用服務(wù)器端的任意多條線程同時(shí)下載(理論上).?
??? 假設(shè)這個(gè)線程數(shù)目是 50 條,本應(yīng)用就將更多的得到服務(wù)器 CPU 的照顧超過(guò) 50 倍.?
??? 但是總歸會(huì)受本地網(wǎng)絡(luò)速度的限制。?

3. 每條線程要負(fù)責(zé)下載的數(shù)據(jù)長(zhǎng)度可以用 “下載數(shù)據(jù)的總長(zhǎng)度” 除以 “參與下載的線程總數(shù)” 來(lái)計(jì)算。但是要考慮到不能整除的情況。?
??? 假設(shè)有 5 條線程參與下載,那么計(jì)算公式應(yīng)該為 :?
??????????? int block = 數(shù)據(jù)總長(zhǎng)度%線程數(shù) == 0? 10/3 : 10/3+1; (不能整除,則加一)?

4. 和數(shù)據(jù)庫(kù)分頁(yè)查詢類型。每條線程需要知道自己從數(shù)據(jù)的什么位置開始下載,下載到什么位置為止。?
?? 首先,為每一個(gè)線程配備一個(gè) id , id 從零開始,為 0 1 2 3...?
?? 開始位置:線程 id 乘以每條線程負(fù)責(zé)下載的數(shù)據(jù)長(zhǎng)度.?
?? 結(jié)束位置:下一個(gè)線程開始位置的前一個(gè)位置。?
?? 如:?
????? int startPosition =? 線程id * 每條線程下載的數(shù)據(jù)長(zhǎng)度?
????? int endPosition = (線程id + 1) * 每條線程下載的數(shù)據(jù)長(zhǎng)度 -1;?
????????
5. HTTP 協(xié)議的 Range 頭可以指定從文件的什么位置開始下載,下載到什么位置結(jié)束。單位為 1byte?
?? Range:bytes=2097152-4194304 表示從文件的 2M 的位置開始下載,下載到 4M 處結(jié)束?
?? 假如 Range 指定要讀取到 文件的 5104389 的字節(jié)數(shù)位置,但是下載的文件本身只有 4104389 個(gè)長(zhǎng)度。那么下載操作自動(dòng)會(huì)在 4104389 處停止。?
?? 因此不會(huì)下載到多余的無(wú)效數(shù)據(jù).?

6. 另一個(gè)難題是如何按順序?qū)?shù)據(jù)寫往本地文件。因?yàn)?#xff0c;線程是同步執(zhí)行的,它們同時(shí)在往本地目標(biāo)文件寫入數(shù)據(jù)。
?? 而線程于線程之間寫入的數(shù)據(jù)并沒(méi)有按照下載數(shù)據(jù)本身的順序。若按照普通的 OutputStream 的寫入方式,最后的本地下載文件將失真。?
?? 于是我們將用到下面這個(gè)類:?
???? java.io.RandomAccessFile?
???? 因?yàn)榇祟愅瑫r(shí)實(shí)現(xiàn)了 DataOutput 和 DataInput 的方法。使他們同時(shí)具有寫入和讀取功能。?
???? 這個(gè)類仿佛存在一個(gè)類似文件指針的東西,可以隨意執(zhí)行文件的任意一個(gè)位置開始讀寫.?
???? 因此此類的實(shí)例支持對(duì)隨機(jī)訪問(wèn)文件的讀取和寫入.?
?????
???? 例如:?
???????

Java代碼??
  • File?file?=?new?File("1.txt");??
  • ????????RandomAccessFile?accessFile?=?new?RandomAccessFile(file,"rwd");??
  • ????????accessFile.setLength(1024);??
  • ????
    ??? 雖然,執(zhí)行完這段代碼后,我們還沒(méi)有向目標(biāo)文件 "1.txt" 寫入任何數(shù)據(jù)。但是如果此時(shí)查看其大小,已經(jīng)為 1kb 了。這是我們自己設(shè)置的大小。?
    ??? 這個(gè)操作類似于向這個(gè)文件存儲(chǔ)了一個(gè)大型的 byte 數(shù)組。這個(gè)數(shù)組將這個(gè)文件撐到指定大小。等待被填滿。?
    ??? 既然是這樣,好處就在于,我們可以通過(guò) “索引” 隨機(jī)訪問(wèn)此文件系統(tǒng)的某個(gè)部分。?
    ??? 例如,可能這個(gè)文件大小為 500?
    ??? 那么,我的業(yè)務(wù)需求可能需要第一次 從 300 位置開始寫數(shù)據(jù),寫到 350 為止。?
    ??? 第二次,我又從 50 開始寫數(shù)據(jù),寫到 100 為止。?
    ??? 總之,我不是 “一次性” 的 “按順序” 的將這個(gè)文件寫完。?
    ??? 那么,RandomAccessFile 可以支持這種操作。?
    ?????
    ???? API?
    ????? void setLength(long newLength)?
    ????????? Sets the length of this file. (設(shè)置文件的預(yù)計(jì)大小)?
    ????? void seek(long pos)?
    ????????? Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs.?
    ????????? 假設(shè)為這個(gè)方法傳入 1028 這個(gè)參數(shù),表示,將從文件的 1028 位置開始寫入。?
    ????? void write(byte[] b, int off, int len)?
    ????????? Writes len bytes from the specified byte array starting at offset off to this file.?
    ????? write(byte[] b)?
    ????????? Writes b.length bytes from the specified byte array to this file, starting at the current file pointer.?
    ????? void writeUTF(String str)?
    ????????? Writes a string to the file using modified UTF-8 encoding in a machine-independent manner.?
    ?????? String readLine()?
    ????????? Reads the next line of text from this file.?

    ????? 實(shí)驗(yàn)代碼:?
    ?????

    Java代碼??
  • public?static?void?main(String[]?args)?throws?Exception?{??
  • ??
  • ?File?file?=?new?File("1.txt");???
  • ??
  • RandomAccessFile?accessFile?=?new?RandomAccessFile(file,"rwd");??
  • ??
  • ?/*?設(shè)置文件為?3?個(gè)字節(jié)大小?*/??
  • ??
  • ?accessFile.setLength(3);??
  • ??
  • ?/*?向第二個(gè)位置寫入?'2'?*/??
  • ??
  • ?accessFile.seek(1);??
  • ??
  • ?accessFile.write("2".getBytes());??
  • ??
  • ?/*?向第一個(gè)位置寫入?'1'?*/???
  • ??
  • accessFile.seek(0);?accessFile.write("1".getBytes());???
  • ??
  • /*?向第三個(gè)位置寫入?'3'?*/??
  • ??
  • ?accessFile.seek(2);???
  • ??
  • accessFile.write("3".getBytes());?accessFile.close();???
  • ??
  • //?期待文件的內(nèi)容為?:123??
  • ??
  • ?}???


  • ????
    ??? 以上實(shí)驗(yàn)成功,雖然我們寫入字符串的順序?yàn)?"2"、"1"、"3",但是因?yàn)樵O(shè)置了文件偏移量的關(guān)系,文件最終保存的數(shù)據(jù)為 : 123?
    ??? 另一個(gè)疑問(wèn),寫完這三個(gè)數(shù)據(jù),文件的大小已經(jīng)為 3 個(gè)字節(jié)大小了。已經(jīng)撐滿了寫入的數(shù)據(jù),那么我們繼續(xù)往里面放數(shù)據(jù)會(huì)有什么效果??
    ????
    ??? /* 向超出大小的第四個(gè)字節(jié)位置寫入數(shù)據(jù) */?
    ??? accessFile.seek(3);?
    ??? accessFile.write("400".getBytes());?
    ????
    ??? 以上代碼無(wú)論 seek 方法指定的文件指針偏移量以及存入的數(shù)據(jù),都已經(jīng)超出了最開始為文件設(shè)定的 3 個(gè)字節(jié)的大小。?
    ??? 按照我的猜測(cè),至少 “accessFile.seek(3)” 位置會(huì)拋出 "ArrayIndexOutOfBoundsException" 異常,表示下標(biāo)越界。?
    ??? 而,單獨(dú)執(zhí)行 "accessFile.write("400".getBytes())" 應(yīng)該可以成功。因?yàn)檫@個(gè)需求屬于合理的,應(yīng)該有執(zhí)行它的機(jī)制。?
    ??? 實(shí)驗(yàn)結(jié)果是兩句代碼都是成功的。貌似是說(shuō)明,文件隱含的大型的字節(jié)數(shù)組,可以自動(dòng)撐大。?
    ????
    ??? 但是要注意的問(wèn)題是,必須要保證所設(shè)定的文件大小的每一個(gè)位置都具有合法的數(shù)據(jù),至少不能為空。?
    ??? 例如:?
    ??????? /* 向第三個(gè)位置寫入 '3'? */?
    ??????? accessFile.seek(2);?
    ??????? accessFile.write("3".getBytes());?
    ????????
    ??????? accessFile.seek(5);?
    ??????? accessFile.write("400".getBytes());?
    ??? 那么結(jié)合之前的代碼,最后的結(jié)果為:?
    ??????? 123口口400?
    ??? 在空白的兩個(gè)位置處出現(xiàn)了亂碼。這是理所應(yīng)當(dāng)?shù)摹?
    ????
    ??? 另外,假設(shè)我們?yōu)槲募付艘话賯€(gè)長(zhǎng)度:?
    ??????? accessFile.setLength(100);?
    ??? 而,實(shí)際上,我們只為其前五個(gè)位置設(shè)置了值。那么理所當(dāng)然的是,文件保存的數(shù)據(jù),最后會(huì)綴上 95 個(gè)亂碼。?
    ????
    7. 準(zhǔn)備工作應(yīng)該十分充分了。接下來(lái)上代碼。?


    ???

    Java代碼??
  • import?java.io.File;????
  • import?java.io.IOException;????
  • import?java.io.InputStream;????
  • import?java.io.RandomAccessFile;????
  • import?java.net.HttpURLConnection;????
  • import?java.net.URL;????
  • /**??
  • ?*?多線程方式文件下載??
  • ?*/????
  • public?class?MulThreadDownload?{????
  • ????/*?下載的URL?*/????
  • ????private?URL?downloadUrl;????
  • ????/*?用于保存的本地文件?*/????
  • ????private?File?localFile;????
  • ????/*?沒(méi)條線程下載的數(shù)據(jù)長(zhǎng)度?*/????
  • ????private?int?block;????
  • ????public?static?void?main(String[]?args)?{????
  • ????????/*?可以為網(wǎng)絡(luò)上任意合法下載地址?*/????
  • ????????String?downPath?=?"http://192.168.1.102:8080/myvideoweb/down.avi";????
  • ????????MulThreadDownload?threadDownload?=?new?MulThreadDownload();????
  • ????????/*?開?10?條線程下載下載?*/????
  • ????????try?{????
  • ????????????threadDownload.download(downPath,?10);????
  • ????????}?catch?(Exception?e)?{????
  • ????????????e.printStackTrace();????
  • ????????}????
  • ????}????
  • ????/**??
  • ?????*?多線程文件下載??
  • ?????*???
  • ?????*?@param?path?下載地址??
  • ?????*?@param?threadCount?線程數(shù)??
  • ?????*/????
  • ????public?void?download(String?path,?int?threadCount)?throws?Exception?{????
  • ????????downloadUrl?=?new?URL(path);????
  • ????????HttpURLConnection?conn?=?(HttpURLConnection)?downloadUrl????
  • ????????????????.openConnection();????
  • ????????/*?設(shè)置?GET?請(qǐng)求方式?*/????
  • ????????conn.setRequestMethod("GET");????
  • ????????/*?設(shè)置響應(yīng)時(shí)間超時(shí)為?5?秒?*/????
  • ????????conn.setConnectTimeout(5?*?1000);????
  • ????????/*?獲取本地文件名?*/????
  • ????????String?filename?=?parseFilename(path);????
  • ????????/*?獲取下載文件的總大小?*/????
  • ????????int?dataLen?=?conn.getContentLength();????
  • ????????if?(dataLen?<?0)?{????
  • ????????????System.out.println("獲取數(shù)據(jù)失敗");????
  • ????????????return;????
  • ????????}????
  • ????????/*?創(chuàng)建本地目標(biāo)文件,并設(shè)置其大小為準(zhǔn)備下載文件的總大小?*/????
  • ????????localFile?=?new?File(filename);????
  • ????????RandomAccessFile?accessFile?=?new?RandomAccessFile(localFile,?"rwd");????
  • ????????/*?這時(shí)候,其實(shí)本地目錄下,已經(jīng)創(chuàng)建好了一個(gè)大小為下載文件的總大小的文件?*/????
  • ????????accessFile.setLength(dataLen);????
  • ????????accessFile.close();????
  • ????????/*?計(jì)算每條線程要下載的數(shù)據(jù)大小?*/????
  • ????????block?=?dataLen?%?threadCount?==?0???dataLen?/?threadCount?:?dataLen?/?threadCount?+?1;????
  • ????????/*?啟動(dòng)線程下載文件?*/????
  • ????????for?(int?i?=?0;?i?<?threadCount;?i++)?{????
  • ????????????new?DownloadThread(i).start();????
  • ????????}????
  • ????}????
  • ????/**??
  • ?????*?解析文件??
  • ?????*/????
  • ????private?String?parseFilename(String?path)?{????
  • ????????return?path.substring(path.lastIndexOf("/")?+?1);????
  • ????}????
  • ????/**??
  • ?????*?內(nèi)部類:?文件下載線程類??
  • ?????*/????
  • ????private?final?class?DownloadThread?extends?Thread?{????
  • ????????/*?線程?id?*/????
  • ????????private?int?threadid;????
  • ????????/*?開始下載的位置?*/????
  • ????????private?int?startPosition;????
  • ????????/*?結(jié)束下載的位置?*/????
  • ????????private?int?endPosition;????
  • ????????/**??
  • ?????????*?新建一個(gè)下載線程??
  • ?????????*?@param?threadid?線程?id??
  • ?????????*/????
  • ????????public?DownloadThread(int?threadid)?{????
  • ????????????this.threadid?=?threadid;????
  • ????????????startPosition?=?threadid?*?block;????
  • ????????????endPosition?=?(threadid?+?1)?*?block?-?1;????
  • ????????}????
  • ????????@Override????
  • ????????public?void?run()?{????
  • ????????????System.out.println("線程?'"?+?threadid?+?"'啟動(dòng)下載..");????
  • ????????????????
  • ????????????RandomAccessFile?accessFile?=?null;????
  • ????????????try?{????
  • ????????????????/*?設(shè)置從本地文件的什么位置開始寫入數(shù)據(jù)?,"rwd"?表示對(duì)文件具有讀寫刪權(quán)限?*/????
  • ????????????????accessFile?=?new?RandomAccessFile(localFile,?"rwd");????
  • ????????????????accessFile.seek(startPosition);????
  • ????????????????HttpURLConnection?conn?=?(HttpURLConnection)?downloadUrl.openConnection();????
  • ????????????????conn.setRequestMethod("GET");????
  • ????????????????conn.setReadTimeout(5?*?1000);????
  • ????????????????/*?為?HTTP?設(shè)置?Range?屬性,可以指定服務(wù)器返回?cái)?shù)據(jù)的范圍?*/????
  • ????????????????conn.setRequestProperty("Range",?"bytes="?+?startPosition?+?"-"????
  • ????????????????????????+?endPosition);????
  • ????????????????/*?將數(shù)據(jù)寫往本地文件?*/????
  • ????????????????writeTo(accessFile,?conn);????
  • ????????????????????
  • ????????????????System.out.println("線程?'"?+?threadid?+?"'完成下載");????
  • ????????????}?catch?(IOException?e)?{????
  • ????????????????e.printStackTrace();????
  • ????????????}?finally?{????
  • ????????????????try?{????
  • ????????????????????if(accessFile?!=?null)?{????
  • ????????????????????????accessFile.close();????
  • ????????????????????}????
  • ?????????????????}?catch?(IOException?ex)?{????
  • ?????????????????????ex.printStackTrace();????
  • ?????????????????}????
  • ????????????}????
  • ????????}????
  • ????????/**??
  • ?????????*?將下載數(shù)據(jù)寫往本地文件??
  • ?????????*/????
  • ????????private?void?writeTo(RandomAccessFile?accessFile,????
  • ????????????????HttpURLConnection?conn){????
  • ????????????InputStream?is?=?null;????
  • ????????????try?{????
  • ????????????????is?=?conn.getInputStream();????
  • ????????????????byte[]?buffer?=?new?byte[1024];????
  • ????????????????int?len?=?-1;????
  • ????????????????while?((len?=?is.read(buffer))?!=?-1)?{????
  • ????????????????????accessFile.write(buffer,?0,?len);????
  • ????????????????}????
  • ????????????}?catch?(IOException?e)?{????
  • ????????????????e.printStackTrace();????
  • ????????????}?finally?{????
  • ????????????????try?{????
  • ????????????????????if(is?!=?null)?{????
  • ????????????????????????is.close();????
  • ????????????????????}?????
  • ????????????????}?catch?(Exception?ex)?{????
  • ????????????????????ex.printStackTrace();????
  • ????????????????}????
  • ????????????}????
  • ????????}????
  • ????}????
  • } ? ?
  • 轉(zhuǎn)載于:https://www.cnblogs.com/bigben0123/p/4527413.html

    總結(jié)

    以上是生活随笔為你收集整理的通过HTTP协议实现多线程下载的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。