日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

基于Java實現多線程下載并允許斷點續傳

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

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

多線程下載及斷點續傳的實現是使用 HTTP/1.1 引入的 Range 請求參數,可以訪問Web資源的指定區間的內容。雖然實現了多線程及斷點續傳,但還有很多不完善的地方。

包含四個類:

Downloader: 主類,負責分配任務給各個子線程,及檢測進度DownloadFile: 表示要下載的哪個文件,為了能寫輸入到文件的指定位置,使用 RandomAccessFile 類操作文件,多個線程寫同一個文件需要保證線程安全,這里直接調用 getChannel 方法,獲取一個文件通道,FileChannel是線程安全的。DownloadTask: 實際執行下載的線程,獲取 [lowerBound, upperBound] 區間的數據,當下載過程中出現異常時要通知其他線程(使用 AtomicBoolean),結束下載Logger: 實時記錄下載進度,以便續傳時知道從哪開始。感覺這里做的比較差,為了能實時寫出日志及方便地使用Properties類的load/store方法格式化輸入輸出,每次都是打開后再關閉。

演示:

隨便找一個文件下載:

基于Java實現多線程下載并允許斷點續傳

強行結束程序并重新運行:

基于Java實現多線程下載并允許斷點續傳

日志文件:

斷點續傳的關鍵是記錄各個線程的下載進度,這里細節比較多,花了很久。只需要記錄每個線程請求的Range區間極客,每次成功寫數據到文件時,就更新一次下載區間。下面是下載完成后的日志內容。

基于Java實現多線程下載并允許斷點續傳

代碼:

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; // 默認線程數量 private AtomicBoolean canceled; // 取消狀態,如果有一個子線程出現異常,則取消整個下載任務 private DownloadFile file; // 下載的文件對象 private String storageLocation; private final int threadCount; // 線程數量 private long fileSize; // 文件大小 private final String url; private long beginTime; // 開始時間 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('* 繼續上次下載進度[已下載:%.2fMB]:%sn', logger.getWroteSize() / 1014.0 / 1024, url); } else { System.out.println('* 開始下載:' + 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()); } // 分配線程下載 dispatcher(reStart); // 循環打印進度 printDownloadProgress(); } catch (IOException e) { System.err.println('x 創建文件失敗[' + e.getMessage() + ']'); } } /** * 分配器,決定每個線程下載哪個區間的數據 */ private void dispatcher(boolean reStart) { long blockSize = fileSize / threadCount; // 每個線程要下載的數據量 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,找了一下午的錯upperBound = (i == threadCount - 1) ? fileSize-1 : lowerBound + blockSize; } new DownloadTask(url, lowerBound, upperBound, file, canceled, threadID).start(); } } /** * 循環打印進度,直到下載完畢,或任務被取消 */ 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('下載進度:%.2f%%, 已下載:%.2fMB,當前速度:%.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 下載失敗,任務已取消'); } else { System.out.println('* 下載成功,本次用時'+ (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('* 連接服務器成功'); } catch (MalformedURLException e) { throw new RuntimeException('URL錯誤'); } catch (IOException e) { System.err.println('x 連接服務器失敗['+ 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; // 下載的文件區間 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('* [線程' + threadId + ']連接成功,開始下載...'); 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('* [線程' + threadId + ']下載完成' + ': ' + lowerBound + '-' + upperBound); } } catch (IOException e) { canceled.set(true); System.err.println('x [線程' + threadId + ']遇到錯誤[' + e.getMessage() + '],結束下載'); } finally { if (input != null) {try { input.close();} catch (IOException e) { e.printStackTrace();} } } } /** * 連接WEB服務器,并返回一個數據通道 * @return 返回通道 * @throws IOException 網絡連接錯誤 */ 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('狀態碼錯誤:' + 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; // 線程安全類 private AtomicLong wroteSize; // 已寫入的長度 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(); } /** * 寫數據 * @param offset 寫偏移量 * @param buffer 數據 * @throws IOException 寫數據出現異常 */ 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 已經下載的數據量,為了知道何時結束整個任務,以及統計信息 */ long getWroteSize() { return wroteSize.get(); } // 繼續下載時調用 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; /** * 重新開始下載時,使用該構造函數 * @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); // 每次寫時都清空文件 log.store(file, null); } catch (IOException e) { e.printStackTrace(); } finally { if (file != null) {try { file.close();} catch (IOException e) { e.printStackTrace();} } } } /** * 獲取區間信息 * 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')); }}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持好吧啦網。

標簽: Java
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
精品一区二区三区中文字幕| 国产精选在线| 日本免费久久| 美女av在线免费看| 日韩欧美二区| 久久国产主播| 国产高清一区| 午夜久久久久| 美女日韩在线中文字幕| 蜜桃一区二区三区在线| 亚洲深深色噜噜狠狠爱网站| 日韩中文字幕一区二区三区| 亚洲综合图色| 久久国产生活片100| 国产精品调教视频| 精品国产aⅴ| 麻豆免费精品视频| 精品国产欧美日韩一区二区三区| 国产精品久久久久久久免费观看 | 国产成人黄色| 亚洲不卡系列| 亚洲一区久久| 青青草精品视频| 成人精品高清在线视频| 日韩三区在线| 久久性天堂网| 色8久久久久| 精品久久不卡| 91成人精品视频| 蜜臀精品一区二区三区在线观看| 日韩欧美中文字幕电影| 日韩二区在线观看| 国产传媒av在线| 亚洲欧洲另类| 欧美精品影院| 日韩精品久久久久久久电影99爱| 亚洲一级在线| 国产欧美另类| 韩国三级一区| 午夜在线视频观看日韩17c| 日韩精品中文字幕一区二区| 97精品国产99久久久久久免费| 精品视频在线观看网站| 国户精品久久久久久久久久久不卡| 最新国产精品视频| 精品国产一区二| 亚洲欧美日韩国产一区二区| 777久久精品| 久久久久亚洲| 日本成人在线网站| 视频二区不卡| 日韩激情中文字幕| 日韩一区二区在线免费| 一二三区精品| 日本在线高清| 日本aⅴ免费视频一区二区三区| 麻豆国产精品一区二区三区| 欧美日韩激情| 国产日韩高清一区二区三区在线 | 香蕉视频成人在线观看| 欧美三级第一页| 成人午夜国产| 国产香蕉精品| 91久久黄色| 精品国产一区二区三区噜噜噜| 午夜国产一区二区| 欧美日韩伊人| 日韩亚洲国产欧美| 成人在线超碰| 日本不卡中文字幕| 亚洲大全视频| 国产精品麻豆久久| 日韩精品福利一区二区三区| 成人精品亚洲| 精品一区二区三区四区五区| 亚洲精品影视| 亚洲高清av| 精品一区二区三区亚洲| 亚洲精品一区二区妖精| 国内精品亚洲| 日本视频中文字幕一区二区三区| 美女网站视频一区| 国产精品www.| 亚洲狼人精品一区二区三区| 丝袜av一区| 福利在线免费视频| 国产精品久久久久久久久免费高清 | 亚洲激情久久| 青青青免费在线视频| 国产精品久久久久久久久久白浆| 日韩欧美三区| 国产伊人精品| 91精品国产调教在线观看| 日韩欧美字幕| 91久久黄色| 天堂成人免费av电影一区 | 亚洲免费中文| 日本欧美大码aⅴ在线播放| 麻豆精品一区二区综合av| 三级欧美在线一区| 精品一区三区| 日韩不卡在线| 精品午夜视频| 国产日本精品| 日韩精品亚洲专区在线观看| 99国产精品视频免费观看一公开| 美女网站视频一区| 成人一区而且| 国产成人精品一区二区免费看京 | 国产a亚洲精品| 亚洲精品美女91| 久久亚洲电影| 视频在线观看国产精品| 国产精品嫩草99av在线| 999久久久亚洲| 成人在线黄色| 97在线精品| 不卡专区在线| 国产一区二区精品福利地址| 国产成人精品一区二区三区视频 | 亚洲精品福利| 国产精品视区| 久久香蕉精品| 性一交一乱一区二区洋洋av| 国产精品毛片| 免费人成精品欧美精品| 在线精品一区| 日韩精品亚洲aⅴ在线影院| 日韩一区二区三区精品视频第3页| 免费观看在线综合色| 三级欧美韩日大片在线看| 亚洲网址在线观看| 蜜臀91精品国产高清在线观看| 99精品美女| 欧美特黄一区| 综合精品一区| 国产精品久久久久久久久久齐齐 | 老牛影视精品| 国产成人精品福利| 欧美成人基地| 黄色精品网站| 日韩一区免费| 国产精品亚洲四区在线观看| 精品一区二区三区在线观看视频| 日韩欧美网址| 亚洲一区欧美激情| 亚洲精品极品| 国产福利一区二区三区在线播放| 久久99偷拍| 日韩av免费大片| 久久中文字幕av| 在线免费观看亚洲| 欧美a一区二区| 久久久久免费| 亚洲精品成人| 日韩精品一区二区三区免费视频| 久久不卡国产精品一区二区| 亚洲播播91| 国产超碰精品| 91久久久精品国产| 只有精品亚洲| 国产精品一级在线观看| 韩国三级一区| 亚洲人成毛片在线播放女女| 欧美天堂视频| 成人综合一区| 亚洲一区二区三区四区电影| 国产精品17p| 久久久91麻豆精品国产一区| 在线亚洲一区| 9国产精品视频| 亚洲欧洲专区| 国产亚洲一区二区三区啪| 丝袜亚洲精品中文字幕一区| 999久久久亚洲| jiujiure精品视频播放| 亚洲精品一区二区妖精| 不卡中文字幕| 奇米777国产一区国产二区| 国产精品天天看天天狠| 久久av中文| 国产成人精选| 色网在线免费观看| 国产福利91精品一区二区| 麻豆网站免费在线观看| 99精品网站| 日韩**一区毛片| 国产精品一区二区三区美女| 亚洲啊v在线| 综合干狼人综合首页| 国产日韩免费| av不卡免费看| 91精品久久久久久久久久不卡| 亚洲精品韩国| 亚洲成人精品| 久久99国产精品视频| 日韩中文字幕91| www.九色在线| 亚洲精品乱码日韩| 性色av一区二区怡红|