Java多线程文件分片下载实现的示例代码

 更新时间:2020年03月06日 08:59:14   作者:qylost  
这篇文章主要介绍了Java多线程文件分片下载实现的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

多线程下载介绍

多线程下载技术是很常见的一种下载方案,这种方式充分利用了多线程的优势,在同一时间段内通过多个线程发起下载请求,将需要下载的数据分割成多个部分,每一个线程只负责下载其中一个部分,然后将下载后的数据组装成完整的数据文件,这样便大大加快了下载效率。常见的下载器,迅雷,QQ旋风等都采用了这种技术。

分片下载

所谓分片下载就是要利用多线程的优势,将要下载的文件一块一块的分配到各个线程中去下载,这样就极大的提高了下载速度。

技术难点

并不能说是什么难点,只能说没接触过不知道罢了。

1、如何请求才能拿到数据的特定部分,而非全部?

可以在HTTP请求头中加入Range来标识数据的请求范围/区间,从HTTP/1.1开始可用。

基本用法:

Range: bytes=10-:取第10个字节及后所有数据。

Range: bytes=40-100:取第40个字节到第100个字节之间的数据。

这样我们就能拿到特定部分的数据了,断点续传也可以用这个来实现。

PS:0为开始点。

2、分片后某线程下载时如何写出?

思路1:等所有下载完成后进行统一汇总整理然后再一次性写出。

这简直是最笨的思路了,如果文件过大全部拉到内存中,岂不凉凉。

思路2:下载采用多线程,写出时采取数据前后顺序排队写出。

也就是说多线程下载,单线程输出,某种程度解决了内存占用问题,不过效率基本不理想。

思路3:要说还是API香,老大哥Java给我们提供了一个类叫做RandomAccessFile。

这个类可以进行随机文件读写,其中有一个seek函数,可以将指针指向任意位置,然后进行读写。什么意思呢,举个栗子:假如我们开了30个线程,首先第一个下载完成的是线程X,它下载的数据范围是4000-9000,那么这时我们调用seek函数将指针拨动到4000,然后调用它的write函数将byte写出,这时4000之前都是NULL,4000之后就是我们插入的数据。这样就可以实现多线程下载和本地写入了。

具体实现

一个分片下载类,我们需要创建多个对象来进行下载。

public class UnitDownloader implements Runnable {
  private int from;
  private int to;
  private File target;
  private String uri;
  private int id;

  public UnitDownloader(int from, int to, File target, String uri, int id) {
    this.from = from;
    this.to = to;
    this.target = target;
    this.uri = uri;
    this.id = id;
  }

  public int getFrom() {
    return from;
  }

  public int getTo() {
    return to;
  }

  @Override
  public void run() {
    //download and save data
    try {
      HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
      connection.setRequestProperty("Range", "bytes=" + from + "-" + to);
      connection.connect();
      int totalSize = connection.getContentLength();
      InputStream inputStream = connection.getInputStream();
      RandomAccessFile randomAccessFile = new RandomAccessFile(target, "rw");
      randomAccessFile.seek(from);
      byte[] buffer = new byte[1024 * 1024];
      int readCount = inputStream.read(buffer, 0, buffer.length);
      while (readCount > 0) {
        totalSize -= readCount;
        System.out.println("分片:" + this.id + "的剩余:" + totalSize);
        randomAccessFile.write(buffer, 0, readCount);
        readCount = inputStream.read(buffer, 0, buffer.length);
      }
      inputStream.close();
      randomAccessFile.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

分片下载管理器,主要就是拿到内容的总大小,将其分配给每一个UnitDownloader。这里的threadCount函数可以再考虑优化一下。

public class MultipleThreadDownloadManager implements Runnable {
  private String uri;
  private File target;

  public MultipleThreadDownloadManager(String uri, File target) {
    this.target = target;
    this.uri = uri;
    if (target.exists() == false) {
      try {
        target.createNewFile();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * 开始下载
   */
  public void start() {
    new Thread(this).start();
  }


  /**
   * 根据文件总大小计算线程数量
   *
   * @param totalSize
   * @return
   */
  public int threadCount(int totalSize) {
    if (totalSize < 30 * 2014 * 1024) {
      return 1;
    }
    return 30;
  }


  @Override
  public void run() {
    //获取文件总大小
    int totalSize = 0;
    try {
      HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
      connection.connect();
      int contentLength = connection.getContentLength();
      totalSize = contentLength;
    } catch (IOException e) {
      e.printStackTrace();
    }
    //将文件分片并分开下载
    int threadCount = threadCount(totalSize);
    int perThreadSize = totalSize / threadCount;//每一个线程分到的任务下载量
    int id = 0;
    int from = 0, to = 0;
    while (totalSize > 0) {
      id++;
      //计算分片
      if (totalSize < perThreadSize) {
        from = 0;
        to = totalSize;
      } else {
        from = totalSize;
        to = from + perThreadSize;
      }
      //开始下载
      UnitDownloader downloader = new UnitDownloader(from, to, target, uri, id);
      new Thread(downloader).start();
    }
  }
}

参考文献

1、https://emacsist.github.io/2015/12/29/http-%E5%8D%8F%E8%AE%AE%E4%B8%AD%E7%9A%84range%E8%AF%B7%E6%B1%82%E5%A4%B4%E4%BE%8B%E5%AD%90/

2、https://blog.csdn.net/lyt_7cs1dn9/article/details/75105266

到此这篇关于Java多线程文件分片下载实现的示例代码的文章就介绍到这了,更多相关Java多线程分片下载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 通过实例解析spring bean之间的关系

    通过实例解析spring bean之间的关系

    这篇文章主要介绍了通过实例解析spring bean之间的关系,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • Springboot接收前端的Json但是为null问题

    Springboot接收前端的Json但是为null问题

    这篇文章主要介绍了Springboot接收前端的Json但是为null问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教<BR>
    2024-05-05
  • SpringBoot整合Minio实现文件上传和读取功能

    SpringBoot整合Minio实现文件上传和读取功能

    最近有一个需求是关于视频上传播放的,需要设计一个方案,中间谈到了Minio这个技术,于是来学习一下,所以本文给大家介绍了SpringBoot整合Minio实现文件上传和读取功能,文中有详细的代码示例供大家参考,需要的朋友可以参考下
    2024-07-07
  • Java 天生就是多线程

    Java 天生就是多线程

    这篇文章主要介绍了Java天生就是多线程,程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程,更多相关内容需要的小伙伴可以参考一下
    2022-07-07
  • Java 中jasperReport实现动态列打印的实现代码

    Java 中jasperReport实现动态列打印的实现代码

    这篇文章主要介绍了Java 中jasperReport实现动态列打印的实现代码的相关资料,希望通过本文大家能掌握这部分内容,需要的朋友可以参考下
    2017-09-09
  • 一文详解Elasticsearch和MySQL之间的数据同步问题

    一文详解Elasticsearch和MySQL之间的数据同步问题

    Elasticsearch中的数据是来自于Mysql数据库的,因此当数据库中的数据进行增删改后,Elasticsearch中的数据,索引也必须跟着做出改变。本文主要来和大家探讨一下Elasticsearch和MySQL之间的数据同步问题,感兴趣的可以了解一下
    2023-04-04
  • Java IO流与NIO技术综合应用详细实例代码

    Java IO流与NIO技术综合应用详细实例代码

    这篇文章主要给大家介绍了关于Java IO流与NIO技术综合应用的相关资料,文中包括了字节流和字符流,以及它们的高级特性如缓冲区、序列化和反序列化,同时还介绍了NIO中的通道和缓冲区,以及选择器的使用,需要的朋友可以参考下
    2024-12-12
  • SpringBoot常用注解详细整理

    SpringBoot常用注解详细整理

    大家好,本篇文章主要讲的是SpringBoot常用注解详细整理,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • Java设计模式之建造者模式实例详解

    Java设计模式之建造者模式实例详解

    这篇文章主要介绍了Java设计模式之建造者模式,结合具体实例形式分析了建造者模式的概念、原理、实现方法与相关使用注意事项,需要的朋友可以参考下
    2017-09-09
  • Spring加载属性文件方式(自动加载优先级问题)

    Spring加载属性文件方式(自动加载优先级问题)

    这篇文章主要介绍了Spring加载属性文件方式(自动加载优先级问题),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02

最新评论