电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

基于Java實(shí)現(xiàn)多線(xiàn)程下載并允許斷點(diǎn)續(xù)傳

瀏覽:39日期:2022-09-04 15:13:17

完整代碼:https://github.com/iyuanyb/Downloader

多線(xiàn)程下載及斷點(diǎn)續(xù)傳的實(shí)現(xiàn)是使用 HTTP/1.1 引入的 Range 請(qǐng)求參數(shù),可以訪問(wèn)Web資源的指定區(qū)間的內(nèi)容。雖然實(shí)現(xiàn)了多線(xiàn)程及斷點(diǎn)續(xù)傳,但還有很多不完善的地方。

包含四個(gè)類(lèi):

Downloader: 主類(lèi),負(fù)責(zé)分配任務(wù)給各個(gè)子線(xiàn)程,及檢測(cè)進(jìn)度DownloadFile: 表示要下載的哪個(gè)文件,為了能寫(xiě)輸入到文件的指定位置,使用 RandomAccessFile 類(lèi)操作文件,多個(gè)線(xiàn)程寫(xiě)同一個(gè)文件需要保證線(xiàn)程安全,這里直接調(diào)用 getChannel 方法,獲取一個(gè)文件通道,F(xiàn)ileChannel是線(xiàn)程安全的。DownloadTask: 實(shí)際執(zhí)行下載的線(xiàn)程,獲取 [lowerBound, upperBound] 區(qū)間的數(shù)據(jù),當(dāng)下載過(guò)程中出現(xiàn)異常時(shí)要通知其他線(xiàn)程(使用 AtomicBoolean),結(jié)束下載Logger: 實(shí)時(shí)記錄下載進(jìn)度,以便續(xù)傳時(shí)知道從哪開(kāi)始。感覺(jué)這里做的比較差,為了能實(shí)時(shí)寫(xiě)出日志及方便地使用Properties類(lèi)的load/store方法格式化輸入輸出,每次都是打開(kāi)后再關(guān)閉。

演示:

隨便找一個(gè)文件下載:

基于Java實(shí)現(xiàn)多線(xiàn)程下載并允許斷點(diǎn)續(xù)傳

強(qiáng)行結(jié)束程序并重新運(yùn)行:

基于Java實(shí)現(xiàn)多線(xiàn)程下載并允許斷點(diǎn)續(xù)傳

日志文件:

斷點(diǎn)續(xù)傳的關(guān)鍵是記錄各個(gè)線(xiàn)程的下載進(jìn)度,這里細(xì)節(jié)比較多,花了很久。只需要記錄每個(gè)線(xiàn)程請(qǐng)求的Range區(qū)間極客,每次成功寫(xiě)數(shù)據(jù)到文件時(shí),就更新一次下載區(qū)間。下面是下載完成后的日志內(nèi)容。

基于Java實(shí)現(xiàn)多線(xiàn)程下載并允許斷點(diǎn)續(xù)傳

代碼:

Downloader.java

package downloader; import java.io.*;import java.net.*;import java.nio.file.Files;import java.nio.file.Path;import java.util.concurrent.atomic.AtomicBoolean; public class Downloader { private static final int DEFAULT_THREAD_COUNT = 4; // 默認(rèn)線(xiàn)程數(shù)量 private AtomicBoolean canceled; // 取消狀態(tài),如果有一個(gè)子線(xiàn)程出現(xiàn)異常,則取消整個(gè)下載任務(wù) private DownloadFile file; // 下載的文件對(duì)象 private String storageLocation; private final int threadCount; // 線(xiàn)程數(shù)量 private long fileSize; // 文件大小 private final String url; private long beginTime; // 開(kāi)始時(shí)間 private Logger logger; public Downloader(String url) { this(url, DEFAULT_THREAD_COUNT); } public Downloader(String url, int threadCount) { this.url = url; this.threadCount = threadCount; this.canceled = new AtomicBoolean(false); this.storageLocation = url.substring(url.lastIndexOf(’/’)+1); this.logger = new Logger(storageLocation + '.log', url, threadCount); } public void start() { boolean reStart = Files.exists(Path.of(storageLocation + '.log')); if (reStart) { logger = new Logger(storageLocation + '.log'); System.out.printf('* 繼續(xù)上次下載進(jìn)度[已下載:%.2fMB]:%sn', logger.getWroteSize() / 1014.0 / 1024, url); } else { System.out.println('* 開(kāi)始下載:' + url); } if (-1 == (this.fileSize = getFileSize())) return; System.out.printf('* 文件大小:%.2fMBn', fileSize / 1024.0 / 1024); this.beginTime = System.currentTimeMillis(); try { this.file = new DownloadFile(storageLocation, fileSize, logger); if (reStart) {file.setWroteSize(logger.getWroteSize()); } // 分配線(xiàn)程下載 dispatcher(reStart); // 循環(huán)打印進(jìn)度 printDownloadProgress(); } catch (IOException e) { System.err.println('x 創(chuàng)建文件失敗[' + e.getMessage() + ']'); } } /** * 分配器,決定每個(gè)線(xiàn)程下載哪個(gè)區(qū)間的數(shù)據(jù) */ private void dispatcher(boolean reStart) { long blockSize = fileSize / threadCount; // 每個(gè)線(xiàn)程要下載的數(shù)據(jù)量 long lowerBound = 0, upperBound = 0; long[][] bounds = null; int threadID = 0; if (reStart) { bounds = logger.getBounds(); } for (int i = 0; i < threadCount; i++) { if (reStart) {threadID = (int)(bounds[i][0]);lowerBound = bounds[i][1];upperBound = bounds[i][2]; } else {threadID = i;lowerBound = i * blockSize;// fileSize-1 !!!!! fu.ck,找了一下午的錯(cuò)upperBound = (i == threadCount - 1) ? fileSize-1 : lowerBound + blockSize; } new DownloadTask(url, lowerBound, upperBound, file, canceled, threadID).start(); } } /** * 循環(huán)打印進(jìn)度,直到下載完畢,或任務(wù)被取消 */ private void printDownloadProgress() { long downloadedSize = file.getWroteSize(); int i = 0; long lastSize = 0; // 三秒前的下載量 while (!canceled.get() && downloadedSize < fileSize) { if (i++ % 4 == 3) { // 每3秒打印一次System.out.printf('下載進(jìn)度:%.2f%%, 已下載:%.2fMB,當(dāng)前速度:%.2fMB/sn', downloadedSize / (double)fileSize * 100 , downloadedSize / 1024.0 / 1024, (downloadedSize - lastSize) / 1024.0 / 1024 / 3);lastSize = downloadedSize;i = 0; } try {Thread.sleep(1000); } catch (InterruptedException ignore) {} downloadedSize = file.getWroteSize(); } file.close(); if (canceled.get()) { try {Files.delete(Path.of(storageLocation)); } catch (IOException ignore) { } System.err.println('x 下載失敗,任務(wù)已取消'); } else { System.out.println('* 下載成功,本次用時(shí)'+ (System.currentTimeMillis() - beginTime) / 1000 +'秒'); } } /** * @return 要下載的文件的尺寸 */ private long getFileSize() { if (fileSize != 0) { return fileSize; } HttpURLConnection conn = null; try { conn = (HttpURLConnection)new URL(url).openConnection(); conn.setConnectTimeout(3000); conn.setRequestMethod('HEAD'); conn.connect(); System.out.println('* 連接服務(wù)器成功'); } catch (MalformedURLException e) { throw new RuntimeException('URL錯(cuò)誤'); } catch (IOException e) { System.err.println('x 連接服務(wù)器失敗['+ e.getMessage() +']'); return -1; } return conn.getContentLengthLong(); } public static void main(String[] args) throws IOException { new Downloader('http://js.xiazaicc.com//down2/ucliulanqi_downcc.zip').start(); }}

DownloadTask.java

package downloader; import java.io.*;import java.net.HttpURLConnection;import java.net.URL;import java.nio.ByteBuffer;import java.nio.channels.Channels;import java.nio.channels.ReadableByteChannel;import java.util.concurrent.atomic.AtomicBoolean; class DownloadTask extends Thread { private final String url; private long lowerBound; // 下載的文件區(qū)間 private long upperBound; private AtomicBoolean canceled; private DownloadFile downloadFile; private int threadId; DownloadTask(String url, long lowerBound, long upperBound, DownloadFile downloadFile, AtomicBoolean canceled, int threadID) { this.url = url; this.lowerBound = lowerBound; this.upperBound = upperBound; this.canceled = canceled; this.downloadFile = downloadFile; this.threadId = threadID; } @Override public void run() { ReadableByteChannel input = null; try { ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024 * 2); // 2MB input = connect(); System.out.println('* [線(xiàn)程' + threadId + ']連接成功,開(kāi)始下載...'); int len; while (!canceled.get() && lowerBound <= upperBound) {buffer.clear();len = input.read(buffer);downloadFile.write(lowerBound, buffer, threadId, upperBound);lowerBound += len; } if (!canceled.get()) {System.out.println('* [線(xiàn)程' + threadId + ']下載完成' + ': ' + lowerBound + '-' + upperBound); } } catch (IOException e) { canceled.set(true); System.err.println('x [線(xiàn)程' + threadId + ']遇到錯(cuò)誤[' + e.getMessage() + '],結(jié)束下載'); } finally { if (input != null) {try { input.close();} catch (IOException e) { e.printStackTrace();} } } } /** * 連接WEB服務(wù)器,并返回一個(gè)數(shù)據(jù)通道 * @return 返回通道 * @throws IOException 網(wǎng)絡(luò)連接錯(cuò)誤 */ private ReadableByteChannel connect() throws IOException { HttpURLConnection conn = (HttpURLConnection)new URL(url).openConnection(); conn.setConnectTimeout(3000); conn.setRequestMethod('GET'); conn.setRequestProperty('Range', 'bytes=' + lowerBound + '-' + upperBound);// System.out.println('thread_'+ threadId +': ' + lowerBound + '-' + upperBound); conn.connect(); int statusCode = conn.getResponseCode(); if (HttpURLConnection.HTTP_PARTIAL != statusCode) { conn.disconnect(); throw new IOException('狀態(tài)碼錯(cuò)誤:' + statusCode); } return Channels.newChannel(conn.getInputStream()); }}

DownloadFile.java

package downloader; import java.io.IOException;import java.io.RandomAccessFile;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;import java.util.concurrent.atomic.AtomicLong; class DownloadFile { private final RandomAccessFile file; private final FileChannel channel; // 線(xiàn)程安全類(lèi) private AtomicLong wroteSize; // 已寫(xiě)入的長(zhǎng)度 private Logger logger; DownloadFile(String fileName, long fileSize, Logger logger) throws IOException { this.wroteSize = new AtomicLong(0); this.logger = logger; this.file = new RandomAccessFile(fileName, 'rw'); file.setLength(fileSize); channel = file.getChannel(); } /** * 寫(xiě)數(shù)據(jù) * @param offset 寫(xiě)偏移量 * @param buffer 數(shù)據(jù) * @throws IOException 寫(xiě)數(shù)據(jù)出現(xiàn)異常 */ void write(long offset, ByteBuffer buffer, int threadID, long upperBound) throws IOException { buffer.flip(); int length = buffer.limit(); while (buffer.hasRemaining()) { channel.write(buffer, offset); } wroteSize.addAndGet(length); logger.updateLog(threadID, length, offset + length, upperBound); // 更新日志 } /** * @return 已經(jīng)下載的數(shù)據(jù)量,為了知道何時(shí)結(jié)束整個(gè)任務(wù),以及統(tǒng)計(jì)信息 */ long getWroteSize() { return wroteSize.get(); } // 繼續(xù)下載時(shí)調(diào)用 void setWroteSize(long wroteSize) { this.wroteSize.set(wroteSize); } void close() { try { file.close(); } catch (IOException e) { e.printStackTrace(); } }}

Logger.java

package downloader; import java.io.*;import java.util.Properties; class Logger { private String logFileName; // 下載的文件的名字 private Properties log; /** * 重新開(kāi)始下載時(shí),使用該構(gòu)造函數(shù) * @param logFileName */ Logger(String logFileName) { this.logFileName = logFileName; log = new Properties(); FileInputStream fin = null; try { log.load(new FileInputStream(logFileName)); } catch (IOException ignore) { } finally { try {fin.close(); } catch (Exception ignore) {} } } Logger(String logFileName, String url, int threadCount) { this.logFileName = logFileName; this.log = new Properties(); log.put('url', url); log.put('wroteSize', '0'); log.put('threadCount', String.valueOf(threadCount)); for (int i = 0; i < threadCount; i++) { log.put('thread_' + i, '0-0'); } } synchronized void updateLog(int threadID, long length, long lowerBound, long upperBound) { log.put('thread_'+threadID, lowerBound + '-' + upperBound); log.put('wroteSize', String.valueOf(length + Long.parseLong(log.getProperty('wroteSize')))); FileOutputStream file = null; try { file = new FileOutputStream(logFileName); // 每次寫(xiě)時(shí)都清空文件 log.store(file, null); } catch (IOException e) { e.printStackTrace(); } finally { if (file != null) {try { file.close();} catch (IOException e) { e.printStackTrace();} } } } /** * 獲取區(qū)間信息 * ret[i][0] = threadID, ret[i][1] = lowerBoundID, ret[i][2] = upperBoundID * @return */ long[][] getBounds() { long[][] bounds = new long[Integer.parseInt(log.get('threadCount').toString())][3]; int[] index = {0}; log.forEach((k, v) -> { String key = k.toString(); if (key.startsWith('thread_')) {String[] interval = v.toString().split('-');bounds[index[0]][0] = Long.parseLong(key.substring(key.indexOf('_') + 1));bounds[index[0]][1] = Long.parseLong(interval[0]);bounds[index[0]++][2] = Long.parseLong(interval[1]); } }); return bounds; } long getWroteSize() { return Long.parseLong(log.getProperty('wroteSize')); }}

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持好吧啦網(wǎng)。

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 铣床|万能铣床|立式铣床|数控铣床|山东滕州万友机床有限公司 | 热缩管切管机-超声波切带机-织带切带机-无纺布切布机-深圳市宸兴业科技有限公司 | 济南网站策划设计_自适应网站制作_H5企业网站搭建_济南外贸网站制作公司_锐尚 | 超声波电磁流量计-液位计-孔板流量计-料位计-江苏信仪自动化仪表有限公司 | 防潮防水通风密闭门源头实力厂家 - 北京酷思帝克门窗 | 酒瓶_酒杯_玻璃瓶生产厂家_徐州明政玻璃制品有限公司 | 公交驾校-北京公交驾校欢迎您! 工作心得_读书心得_学习心得_找心得体会范文就上学道文库 | 无刷电机_直流无刷电机_行星减速机-佛山市藤尺机电设备有限公司 无菌检查集菌仪,微生物限度仪器-苏州长留仪器百科 | 小型气象站_便携式自动气象站_校园气象站-竞道气象设备网 | 安徽泰科检测科技有限公司【官方网站】 | 卧涛科技有限公司科技项目申报公司|高新技术企业申报|专利申请 | 天津试验仪器-电液伺服万能材料试验机,恒温恒湿标准养护箱,水泥恒应力压力试验机-天津鑫高伟业科技有限公司 | 探伤仪,漆膜厚度测试仪,轮胎花纹深度尺厂家-淄博创宇电子 | 防爆电机生产厂家,YBK3电动机,YBX3系列防爆电机,YBX4节防爆电机--河南省南洋防爆电机有限公司 | 南京技嘉环保科技有限公司-杀菌除臭剂|污水|垃圾|厕所|橡胶厂|化工厂|铸造厂除臭剂 | 温泉机设备|温泉小镇规划设计|碳酸泉设备 - 大连连邦温泉科技 | 上海办公室装修,办公楼装修设计,办公空间设计,企业展厅设计_写艺装饰公司 | 今日娱乐圈——影视剧集_八卦娱乐_明星八卦_最新娱乐八卦新闻 | 河南卓美创业科技有限公司-河南卓美防雷公司-防雷接地-防雷工程-重庆避雷针-避雷器-防雷检测-避雷带-避雷针-避雷塔、机房防雷、古建筑防雷等-山西防雷公司 | 欧盟ce检测认证_reach检测报告_第三方检测中心-深圳市威腾检验技术有限公司 | 粉末冶金注射成型厂家|MIM厂家|粉末冶金齿轮|MIM零件-深圳市新泰兴精密科技 | 水冷散热器_水冷电子散热器_大功率散热器_水冷板散热器厂家-河源市恒光辉散热器有限公司 | 三佳互联一站式网站建设服务|网站开发|网站设计|网站搭建服务商 赛默飞Thermo veritiproPCR仪|ProFlex3 x 32PCR系统|Countess3细胞计数仪|371|3111二氧化碳培养箱|Mirco17R|Mirco21R离心机|仟诺生物 | 亚克力制品定制,上海嘉定有机玻璃加工制作生产厂家—官网 | 扒渣机,铁水扒渣机,钢水扒渣机,铁水捞渣机,钢水捞渣机-烟台盛利达工程技术有限公司 | 钣金加工厂家-钣金加工-佛山钣金厂-月汇好 | 恒温槽_恒温水槽_恒温水浴槽-上海方瑞仪器有限公司 | 撕碎机_轮胎破碎机_粉碎机_回收生产线厂家_东莞华达机械有限公司 | 婚博会2024时间表_婚博会门票领取_婚博会地址-婚博会官网 | 机床导轨_导轨板_滚轮导轨-上海旻佑精密机械有限公司 | 凝胶成像仪,化学发光凝胶成像系统,凝胶成像分析系统-上海培清科技有限公司 | 山东成考网-山东成人高考网 | 中医中药治疗血小板减少-石家庄血液病肿瘤门诊部 | 武汉天安盾电子设备有限公司 - 安盾安检,武汉安检门,武汉安检机,武汉金属探测器,武汉测温安检门,武汉X光行李安检机,武汉防爆罐,武汉车底安全检查,武汉液体探测仪,武汉安检防爆设备 | 武汉天安盾电子设备有限公司 - 安盾安检,武汉安检门,武汉安检机,武汉金属探测器,武汉测温安检门,武汉X光行李安检机,武汉防爆罐,武汉车底安全检查,武汉液体探测仪,武汉安检防爆设备 | 免费分销系统 — 分销商城系统_分销小程序开发 -【微商来】 | SMC-SMC电磁阀-日本SMC气缸-SMC气动元件展示网 | 工作心得_读书心得_学习心得_找心得体会范文就上学道文库 | 酵素生产厂家_酵素OEM_酵素加盟_酵素ODM_酵素原料厂家_厦门益力康 | 济宁工业提升门|济宁电动防火门|济宁快速堆积门-济宁市统一电动门有限公司 | 钢板仓,大型钢板仓,钢板库,大型钢板库,粉煤灰钢板仓,螺旋钢板仓,螺旋卷板仓,骨料钢板仓 |