多线程下载文件实践之旅
目錄
1、使用場景
2、多線程下載原理
3、請求如何分段下載
3.1、需要請求的數(shù)據(jù)如何分段。
3.2、分段下載的數(shù)據(jù)如何組裝成完整的數(shù)據(jù)文件。
4、關(guān)鍵代碼實(shí)現(xiàn)
3、成果展現(xiàn)
4、總結(jié)
5、參考文章
1、使用場景
? ? ? ? 因?yàn)樽罱谧霭岩郧霸诎俣裙性粕系囊粢曨l和文檔文件,需要遷移阿里云上。這里面還有一個(gè)小插曲;有位同事想出辦法說郵遞一個(gè)移動硬盤到百度云讓直接Copy到移動硬盤之中。按照正規(guī)流程這個(gè)肯定是不可能的吧,作為一個(gè)大企業(yè);必須的符合規(guī)范方式操作吧。個(gè)人意見應(yīng)該可以通過對應(yīng)銷售人員或者公司里面專門聯(lián)系一個(gè)百度的對接人員;到百度公司協(xié)商作為一個(gè)遷移項(xiàng)目形式;該付款付款。這樣還可能實(shí)現(xiàn)。最后本人只能通過使用Baidu提供API文檔;獲得原始音視頻文件的網(wǎng)絡(luò)Url路徑。自己寫相關(guān)的多線程下載文件。最后分別把累加賬號上230G音視頻文件下載完畢;另外一個(gè)賬號850G下載完畢。
2、多線程下載原理
- 客戶端要下載一個(gè)文件, 首先請求服務(wù)器,服務(wù)器將這個(gè)文件傳送給客戶端,客戶端保存到本地, 完成了一個(gè)下載的過程.
- 多線程下載的思想是客戶端開啟多個(gè)線程同時(shí)下載,每個(gè)線程只負(fù)責(zé)下載文件的一部分, 當(dāng)所有線程下載完成的時(shí)候,文件下載完畢.
- 并不是線程越多下載越快, 與網(wǎng)絡(luò)環(huán)境有很大的關(guān)系
- 在同等的網(wǎng)絡(luò)環(huán)境下,多線程下載速度要高于單線程.
- 多線程下載占用資源比單線程多,相當(dāng)于用資源換取速度
多線程下載技術(shù)是很常見的一種下載方案,這種方式充分利用了多線程的優(yōu)勢,在同一時(shí)間段內(nèi)通過多個(gè)線程發(fā)起下載請求,將需要下載的數(shù)據(jù)分割成多個(gè)部分,每一個(gè)線程只負(fù)責(zé)下載其中一個(gè)部分,然后將下載后的數(shù)據(jù)組裝成完整的數(shù)據(jù)文件,這樣便大大加快了下載效率。常見的下載器,迅雷,QQ旋風(fēng)等都采用了這種技術(shù)。
3、請求如何分段下載
3.1、需要請求的數(shù)據(jù)如何分段。
Range,是在 HTTP/1.1里新增的一個(gè) header field,它允許客戶端實(shí)際上只請求文檔的一部分,或者說某個(gè)范圍。
??有了范圍請求,HTTP 客戶端可以通過請求曾獲取失敗的實(shí)體的一個(gè)范圍(或者說一部分),來恢復(fù)下載該實(shí)體。當(dāng)然這有一個(gè)前提,那就是從客戶端上一次請求該實(shí)體到這次發(fā)出范圍請求的時(shí)段內(nèi),該對象沒有改變過。例如:
GET /bigfile.html HTTP/1.1 Host: www.joes-hardware.com Range: bytes=4000- User-Agent: Mozilla/4.61 [en] (WinNT; I)? ? ?上述請求中,客戶端請求的是文檔開頭 4000 字節(jié)之后的部分(不必給出結(jié)尾字節(jié)數(shù),因?yàn)檎埱蠓娇赡懿恢牢臋n的大小)。在客戶端收到了開頭的 4000 字節(jié)之后就失敗的情況下,可以使用這種形式的范圍請求。還可以用 Range 首部來請求多個(gè)范圍(這些范圍可以按任意順序給出,也可以相互重疊)。
Range頭域使用形式如下。例如:
表示頭500個(gè)字節(jié):bytes=0-499 表示第二個(gè)500字節(jié):bytes=500-999 表示最后500個(gè)字節(jié):bytes=-500 表示500字節(jié)以后的范圍:bytes=500- 第一個(gè)和最后一個(gè)字節(jié):bytes=0-0,-1服務(wù)器接收到線程3的請求報(bào)文,發(fā)現(xiàn)這是一個(gè)帶有Range頭的GET請求,
如果一切正常,服務(wù)器的**響應(yīng)報(bào)文會有下面這行:
HTTP/1.1 206 OK**
表示處理請求成功,響應(yīng)報(bào)文還有這一行
Content-Range: bytes 200-299/403
斜杠后面的403表示文件的大小
Http協(xié)議的發(fā)展歷程
HTTP協(xié)議到現(xiàn)在為止總共經(jīng)歷了3個(gè)版本的演化,第一個(gè)HTTP協(xié)議誕生于1989年3月。
| HTTP/0.9 | 1991年 |
| HTTP/1.0 | 1992-1996年 |
| HTTP/1.1 | 1997-1999年 |
| HTTP/2.0 | 2012-2014年 |
也就是HTTP/1.1 從1997-1999 年就應(yīng)用了,所以現(xiàn)在基本上是支持?jǐn)帱c(diǎn)續(xù)傳的。
3.2、分段下載的數(shù)據(jù)如何組裝成完整的數(shù)據(jù)文件。
? ?隨機(jī)訪問文件RandomAccessFile類
? ? ?RandomAccessFile適用于由大小已知的記錄組成的文件,所以我們可以使用seek()將記錄從一處轉(zhuǎn)移到另一處,然后讀取或修改記錄。
? ? 隨機(jī)訪問文件的行為類似存儲在文件系統(tǒng)中的一個(gè)大型 byte 數(shù)組。存在指向該隱含數(shù)組的光標(biāo)或索引,稱為文件指針;輸入操作從文件指針開始讀取字節(jié),并隨著對字節(jié)的讀取而前移此文件指針。如果隨機(jī)訪問文件以讀取/寫入模式創(chuàng)建,則輸出操作也可用;輸出操作從文件指針開始寫入字節(jié),并隨著對字節(jié)的寫入而前移此文件指針。寫入隱含數(shù)組的當(dāng)前末尾之后的輸出操作導(dǎo)致該數(shù)組擴(kuò)展。該文件指針可以通過 getFilePointer 方法讀取,并通過 seek 方法設(shè)置。
通過UrlConnection下載部分資源。
??注意:
???1.需要Range頭,key:Range???value:bytes:0-499?
??????????urlconnection.setRequestPropety("Range","bytes:0-499")
???2.需要設(shè)置每個(gè)線程在本地文件的保存的開始位置
??????????RandomAccessFile randomfile =new RandomAccessFile(File file,String mode)
??????????randomfile.seek(int startPostion);//本次線程下載保存的開始位置。
創(chuàng)建從中讀取和向其中寫入(可選)的隨機(jī)訪問文件流,該文件由 File 參數(shù)指定。將創(chuàng)建一個(gè)新的 FileDescriptor 對象來表示此文件的連接。
mode 參數(shù)指定用以打開文件的訪問模式。允許的值及其含意為:
? ? ? “r“——以只讀方式打開。調(diào)用結(jié)果對象的任何 write 方法都將導(dǎo)致拋出 IOException。
? ? ? “rw“——打開以便讀取和寫入。如果該文件尚不存在,則嘗試創(chuàng)建該文件。
? ? ? “rws“—— 打開以便讀取和寫入,對于 “rw”,還要求對文件的內(nèi)容或元數(shù)據(jù)的每個(gè)更新都同步寫入到底層存儲設(shè)備。
? ? “rwd“——打開以便讀取和寫入,對于 “rw”,還要求對文件內(nèi)容的每個(gè)更新都同步寫入到底層存儲設(shè)備。
4、關(guān)鍵代碼實(shí)現(xiàn)
DownloadConstans.java
package com.wdcloud.publiccloud.files.tool.download.filedownload;import java.util.concurrent.*;/*** @Description* @auther jianxiapc* @create 2019-08-20 11:20*/ public class DownloadConstans {public static final int MAX_THREAD_COUNT = getSystemProcessCount();private static final int MAX_IMUMPOOLSIZE = MAX_THREAD_COUNT;/*** 自定義線程池*/private static ExecutorService MY_THREAD_POOL;/*** 自定義線程池*/public static ExecutorService getMyThreadPool(){if(MY_THREAD_POOL == null){MY_THREAD_POOL = Executors.newFixedThreadPool(MAX_IMUMPOOLSIZE);}return MY_THREAD_POOL;}// 線程池private static ThreadPoolExecutor threadPool;/*** 單例,單任務(wù) 線程池* @return*/public static ThreadPoolExecutor getThreadPool(){if(threadPool == null){threadPool = new ThreadPoolExecutor(MAX_IMUMPOOLSIZE, MAX_IMUMPOOLSIZE, 3, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(16),new ThreadPoolExecutor.CallerRunsPolicy());}return threadPool;}/*** 獲取服務(wù)器cpu核數(shù)* @return*/private static int getSystemProcessCount(){//int count =Runtime.getRuntime().availableProcessors();//僅僅只啟動4個(gè)線程進(jìn)行下載int count=4;return count;} }FileMultiPartDownLoad.java
package com.wdcloud.publiccloud.files.tool.download.filedownload;import org.slf4j.Logger; import org.slf4j.LoggerFactory;import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.locks.ReentrantLock;/*** @Description* @auther jianxiapc* @create 2019-08-20 11:02*/ public class FileMultiPartDownLoad {private static Logger logger = LoggerFactory.getLogger(FileMultiPartDownLoad.class);/*** 線程下載成功標(biāo)志*/private static int flag = 0;/*** 服務(wù)器請求路徑*/private String netWorkFileUrlPath;/*** 本地路徑*/private String localPath;/*** 線程計(jì)數(shù)同步輔助*/private CountDownLatch latch;// 定長線程池private static ExecutorService threadPool;public FileMultiPartDownLoad(String netWorkFileUrlPath, String localPath) {this.netWorkFileUrlPath = netWorkFileUrlPath;this.localPath = localPath;}public boolean executeDownLoad() {try {URL url = new URL(netWorkFileUrlPath);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);//設(shè)置超時(shí)時(shí)間conn.setRequestMethod("GET");//設(shè)置請求方式conn.setRequestProperty("Connection", "Keep-Alive");int code = conn.getResponseCode();if (code != 200) {logger.error(String.format("無效網(wǎng)絡(luò)地址:%s", netWorkFileUrlPath));return false;}//服務(wù)器返回的數(shù)據(jù)的長度,實(shí)際上就是文件的長度,單位是字節(jié) // int length = conn.getContentLength(); //文件超過2G會有問題long length = getRemoteFileSize(netWorkFileUrlPath);logger.info("文件總長度:" + length + "字節(jié)(B)");RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");//指定創(chuàng)建的文件的長度raf.setLength(length);raf.close();//分割文件int partCount = DownloadConstans.MAX_THREAD_COUNT;int partSize = (int)(length / partCount);latch = new CountDownLatch(partCount);threadPool = DownloadConstans.getMyThreadPool();for (int threadId = 1; threadId <= partCount; threadId++) {// 每一個(gè)線程下載的開始位置long startIndex = (threadId - 1) * partSize;// 每一個(gè)線程下載的結(jié)束位置long endIndex = startIndex + partSize - 1;if (threadId == partCount) {//最后一個(gè)線程下載的長度稍微長一點(diǎn)endIndex = length;}logger.info("線程" + threadId + "下載:" + startIndex + "字節(jié)~" + endIndex + "字節(jié)");threadPool.execute(new DownLoadThread(threadId, startIndex, endIndex, latch));}latch.await();if(flag == 0){return true;}} catch (Exception e) {logger.error(String.format("文件下載失敗,文件地址:%s,失敗原因:%s", netWorkFileUrlPath, e.getMessage()), e);}return false;}/*** 內(nèi)部類用于實(shí)現(xiàn)下載*/public class DownLoadThread implements Runnable {private Logger logger = LoggerFactory.getLogger(DownLoadThread.class);/*** 線程ID*/private int threadId;/*** 下載起始位置*/private long startIndex;/*** 下載結(jié)束位置*/private long endIndex;private CountDownLatch latch;public DownLoadThread(int threadId, long startIndex, long endIndex, CountDownLatch latch) {this.threadId = threadId;this.startIndex = startIndex;this.endIndex = endIndex;this.latch = latch;}@Overridepublic void run() {try {//logger.info("線程" + threadId + "正在下載...");URL url = new URL(netWorkFileUrlPath);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestProperty("Connection", "Keep-Alive");conn.setRequestMethod("GET");//請求服務(wù)器下載部分的文件的指定位置conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);conn.setConnectTimeout(5000);int code = conn.getResponseCode();//logger.info("線程" + threadId + "請求返回code=" + code);InputStream is = conn.getInputStream();//返回資源RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");//隨機(jī)寫文件的時(shí)候從哪個(gè)位置開始寫raf.seek(startIndex);//定位文件int len = 0;byte[] buffer = new byte[1024];while ((len = is.read(buffer)) != -1) {raf.write(buffer, 0, len);}is.close();raf.close();logger.info("線程" + threadId + "下載完畢");} catch (Exception e) {//線程下載出錯(cuò)FileMultiPartDownLoad.flag = 1;logger.error(e.getMessage(),e);} finally {//計(jì)數(shù)值減一latch.countDown();}}}/*** 內(nèi)部方法,獲取遠(yuǎn)程文件大小* @param remoteFileUrl* @return* @throws IOException*/private long getRemoteFileSize(String remoteFileUrl) throws IOException {long fileSize = 0;HttpURLConnection httpConnection = (HttpURLConnection) new URL(remoteFileUrl).openConnection();httpConnection.setRequestMethod("HEAD");int responseCode = 0;try {responseCode = httpConnection.getResponseCode();} catch (IOException e) {e.printStackTrace();}if (responseCode >= 400) {logger.debug("Web服務(wù)器響應(yīng)錯(cuò)誤!");return 0;}String sHeader;for (int i = 1;; i++) {sHeader = httpConnection.getHeaderFieldKey(i);if (sHeader != null && sHeader.equals("Content-Length")) {fileSize = Long.parseLong(httpConnection.getHeaderField(sHeader));break;}}return fileSize;}/*** 下載文件執(zhí)行器* @param netWorkFileUrlPath* @param localDirPath* @param fileName* @return*/public synchronized static String downLoad(String netWorkFileUrlPath,String localDirPath,String fileName) {ReentrantLock lock = new ReentrantLock();lock.lock();String[] names = netWorkFileUrlPath.split("\\.");if (names == null || names.length <= 0) {return null;}String fileTypeName = names[names.length - 1];String localStorageDirPath =localDirPath+"/" +fileName;System.out.println("localStorageDirPath: "+localStorageDirPath);FileMultiPartDownLoad m = new FileMultiPartDownLoad(netWorkFileUrlPath, localStorageDirPath);long startTime = System.currentTimeMillis();boolean flag = false;try{flag = m.executeDownLoad();long endTime = System.currentTimeMillis();if(flag){logger.info(fileName+" : 文件下載結(jié)束,共耗時(shí)" + (endTime - startTime)+ "ms");return localStorageDirPath;}logger.warn("文件下載失敗");return null;}catch (Exception ex){logger.error(ex.getMessage(),ex);return null;}finally {FileMultiPartDownLoad.flag = 0; // 重置 下載狀態(tài)if(!flag){File file = new File(localStorageDirPath);file.delete();}lock.unlock();}} }調(diào)用方法代碼
/*** 首先通過調(diào)用百度SDK API接口,獲得基本信息,然后使用多線性 下載單個(gè)vod視頻文件* @param bceClient* @param vodMediaId 視頻id* @param fileStorageDiskPath 存儲下載文件路徑* @param excelFileName 下載后保存文件信息得excel* @param expiredInSeconds 過期時(shí)間默認(rèn)3600s*/public void downloadSingleVodMediaFile (VodClient bceClient, String vodMediaId,String fileStorageDiskPath,String excelFileName,long expiredInSeconds) {logger.info("vodMediaId = " + vodMediaId);GetMediaSourceDownloadResponse response = bceClient.getMediaSourceDownload(vodMediaId,expiredInSeconds);String netWorkFileUrl = response.getSourceUrl();logger.info("netWorkFileUrl = " + netWorkFileUrl);//測試線程下載和多線線程下載Date startDate = new Date();long downLoadStartTime=System.currentTimeMillis();//System.out.println("downLoadStartTime: "+downLoadStartTime);logger.info("downLoadStartTime: "+sdf.format(startDate));//OkHttpDownloadUtil.downNetWorkFile(netWorkFileUrl,fileStorageDiskPath,"single.mp4");Map<String, Object> vodFileInfoMap = getVodFileInfoByVodId(bceClient, vodMediaId);String fileName =vodFileInfoMap.get("title").toString();//針對當(dāng)個(gè)文件下載的函數(shù)調(diào)用FileMultiPartDownLoad.downLoad(netWorkFileUrl,fileStorageDiskPath,fileName);Date endDate = new Date();long downLoadEndTime=System.currentTimeMillis();long customDownloadTime=downLoadEndTime-downLoadStartTime;String downloadTimeFormat=CommonConvertUtils.formatMillisTime(customDownloadTime);//System.out.println("downloadTimeFormat: "+downloadTimeFormat);logger.info("文件 "+vodMediaId+" 下載開始時(shí)間"+sdf.format(startDate)+" 下載完成時(shí)間:"+sdf.format(endDate));logger.info("文件 "+vodMediaId+" downloadTimeFormat: "+downloadTimeFormat);}3、成果展現(xiàn)
4、總結(jié)
? ? ? ? 通過本次下載文件相關(guān)代碼編寫深刻認(rèn)識了多線程下載的有點(diǎn);同時(shí)也系統(tǒng)性學(xué)習(xí)一下如何多線程下載文件。同時(shí)也實(shí)踐如何使用多線程去下載文件。
5、參考文章
Java多線程下載原理與實(shí)現(xiàn)
Java使用多線程的好處以及斷點(diǎn)續(xù)傳原理
多線程加速下載的原理
Java--多線程斷點(diǎn)下載
多線程下載和多線程斷點(diǎn)下載的原理
總結(jié)
以上是生活随笔為你收集整理的多线程下载文件实践之旅的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: opencv实战,钢板焊接点寻找1
- 下一篇: 扑克牌游戏,两人接龙(数据结构:队列、栈