欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2020年03月13日 09:09:13   作者:yuanyb  
這篇文章主要介紹了基于Java實(shí)現(xiàn)多線(xiàn)程下載并允許斷點(diǎn)續(xù)傳,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

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

多線(xiàn)程下載及斷點(diǎn)續(xù)傳的實(shí)現(xiàn)是使用 HTTP/1.1 引入的 Range 請(qǐng)求參數(shù),可以訪(fǎng)問(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è)文件下載:

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

日志文件:

斷點(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)容。

代碼:

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]:%s\n", logger.getWroteSize() / 1014.0 / 1024, url);
    } else {
      System.out.println("* 開(kāi)始下載:" + url);
    }
    if (-1 == (this.fileSize = getFileSize()))
      return;
    System.out.printf("* 文件大?。?.2fMB\n", 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/s\n",
            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í)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java線(xiàn)程池FutureTask實(shí)現(xiàn)原理詳解

    Java線(xiàn)程池FutureTask實(shí)現(xiàn)原理詳解

    這篇文章主要介紹了Java線(xiàn)程池FutureTask實(shí)現(xiàn)原理詳解,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • 如何通過(guò)Java添加水印到Word文檔

    如何通過(guò)Java添加水印到Word文檔

    這篇文章主要介紹了如何通過(guò)Java添加水印到Word文檔,水印是一種常用于各種文檔的聲明、防偽手段,一般可設(shè)置文字水印或者加載圖片作為水印。以下內(nèi)容將分享通過(guò)Java編程給Word文檔添加水印效果的方法,需要的朋友可以參考下
    2019-07-07
  • SpringBoot入門(mén)之集成JSP的示例代碼

    SpringBoot入門(mén)之集成JSP的示例代碼

    這篇文章主要介紹了SpringBoot入門(mén)之集成JSP的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • java如何用反射將一個(gè)對(duì)象復(fù)制給另一個(gè)對(duì)象

    java如何用反射將一個(gè)對(duì)象復(fù)制給另一個(gè)對(duì)象

    這篇文章主要介紹了java如何用反射將一個(gè)對(duì)象復(fù)制給另一個(gè)對(duì)象問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • 在Java中使用ModelMapper簡(jiǎn)化Shapefile屬性轉(zhuǎn)JavaBean實(shí)戰(zhàn)過(guò)程

    在Java中使用ModelMapper簡(jiǎn)化Shapefile屬性轉(zhuǎn)JavaBean實(shí)戰(zhàn)過(guò)程

    本文介紹了在Java中使用ModelMapper庫(kù)簡(jiǎn)化Shapefile屬性轉(zhuǎn)JavaBean的過(guò)程,對(duì)比了原始的set方法和構(gòu)造方法,展示了如何使用ModelMapper進(jìn)行動(dòng)態(tài)屬性映射,從而減少手動(dòng)編寫(xiě)轉(zhuǎn)換代碼的工作量,通過(guò)示例代碼,展示了如何使用GeoTools讀取Shapefile屬性并將其轉(zhuǎn)換為JavaBean對(duì)象
    2025-02-02
  • Java實(shí)現(xiàn)樹(shù)形List與扁平List互轉(zhuǎn)的示例代碼

    Java實(shí)現(xiàn)樹(shù)形List與扁平List互轉(zhuǎn)的示例代碼

    在平時(shí)的開(kāi)發(fā)中,我們時(shí)常會(huì)遇到需要將"樹(shù)形List"與"扁平List"互轉(zhuǎn)的情況,本文為大家整理了Java實(shí)現(xiàn)樹(shù)形List與扁平List互轉(zhuǎn)的示例代碼,希望對(duì)大家有所幫助
    2023-05-05
  • RocketMQ?源碼分析Broker消息刷盤(pán)服務(wù)

    RocketMQ?源碼分析Broker消息刷盤(pán)服務(wù)

    這篇文章主要為大家介紹了RocketMQ?源碼分析Broker消息刷盤(pán)服務(wù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • IDEA關(guān)閉git管理,文件變成紅色解決方案

    IDEA關(guān)閉git管理,文件變成紅色解決方案

    在軟件開(kāi)發(fā)中,當(dāng)一個(gè)文件夾內(nèi)的Java項(xiàng)目啟用Git版本控制,通常會(huì)導(dǎo)致該文件夾下所有項(xiàng)目同步開(kāi)啟Git,這種做法有助于保持項(xiàng)目的一致性和可追溯性,但也可能帶來(lái)管理上的復(fù)雜性,如果需要解除某個(gè)項(xiàng)目的Git管理,可以通過(guò)IDE的設(shè)置選項(xiàng)進(jìn)行調(diào)整
    2024-10-10
  • java中switch選擇語(yǔ)句代碼詳解

    java中switch選擇語(yǔ)句代碼詳解

    這篇文章主要介紹了java中switch選擇語(yǔ)句代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • Java利用Dijkstra算法求解拓?fù)潢P(guān)系最短路徑

    Java利用Dijkstra算法求解拓?fù)潢P(guān)系最短路徑

    迪杰斯特拉算法(Dijkstra)是由荷蘭計(jì)算機(jī)科學(xué)迪家迪杰斯特拉于1959年提出的,因此又叫狄克斯特拉算法。本文將利用迪克斯特拉(Dijkstra)算法求拓?fù)潢P(guān)系最短路徑,感興趣的可以了解一下
    2022-07-07

最新評(píng)論