1. 基本原理,每条线程从文件不同的位置开始下载,最后合并出完整的数据。

2. 使用多线程下载的好处 
    下载速度快。为什么呢?很好理解,以往我是一条线程在服务器上下载。也就是说,对应在服务器上,有一个我的下载线程存在。 
    这时候肯定不只我一个人在下载,服务器上肯定同时存在多条下载线程,在下载服务器资源。对于 CPU 来说,不可能实现并发执行。 
    CPU 会公平的为这些线程划分时间片,轮流执行,a线程十毫秒 , b线程十毫秒... 
    假设运用了本文这种手法,意味着我的下载应用,可以同时使用服务器端的任意多条线程同时下载(理论上). 
    假设这个线程数目是 50 条,本应用就将更多的得到服务器 CPU 的照顾超过 50 倍. 
    但是总归会受本地网络速度的限制。

3. 每条线程要负责下载的数据长度可以用 “下载数据的总长度” 除以 “参与下载的线程总数” 来计算。但是要考虑到不能整除的情况。 
    假设有 5 条线程参与下载,那么计算公式应该为 : 
            int block = 数据总长度%线程数 == 0? 10/3 : 10/3+1; (不能整除,则加一)

4. 和数据库分页查询类型。每条线程需要知道自己从数据的什么位置开始下载,下载到什么位置为止。 
   首先,为每一个线程配备一个 id , id 从零开始,为 0 1 2 3... 
   开始位置:线程 id 乘以每条线程负责下载的数据长度. 
   结束位置:下一个线程开始位置的前一个位置。 
   如: 
      int startPosition =  线程id * 每条线程下载的数据长度 
      int endPosition = (线程id + 1) * 每条线程下载的数据长度 -1; 
        
5. HTTP 协议的 Range 头可以指定从文件的什么位置开始下载,下载到什么位置结束。单位为 1byte 
   Range:bytes=2097152-4194304 表示从文件的 2M 的位置开始下载,下载到 4M 处结束 
   假如 Range 指定要读取到 文件的 5104389 的字节数位置,但是下载的文件本身只有 4104389 个长度。那么下载操作自动会在 4104389 处停止。 
   因此不会下载到多余的无效数据.

6. 另一个难题是如何按顺序将数据写往本地文件。因为,线程是同步执行的,它们同时在往本地目标文件写入数据。
   而线程于线程之间写入的数据并没有按照下载数据本身的顺序。若按照普通的 OutputStream 的写入方式,最后的本地下载文件将失真。 
   于是我们将用到下面这个类: 
     java.io.RandomAccessFile 
     因为此类同时实现了 DataOutput 和 DataInput 的方法。使他们同时具有写入和读取功能。 
     这个类仿佛存在一个类似文件指针的东西,可以随意执行文件的任意一个位置开始读写. 
     因此此类的实例支持对随机访问文件的读取和写入. 
     
     例如:

Java代码  
  1. File file = new File("1.txt");
  2. RandomAccessFile accessFile = new RandomAccessFile(file,"rwd");
  3. accessFile.setLength(1024);

虽然,执行完这段代码后,我们还没有向目标文件 "1.txt" 写入任何数据。但是如果此时查看其大小,已经为 1kb 了。这是我们自己设置的大小。 
    这个操作类似于向这个文件存储了一个大型的 byte 数组。这个数组将这个文件撑到指定大小。等待被填满。 
    既然是这样,好处就在于,我们可以通过 “索引” 随机访问此文件系统的某个部分。 
    例如,可能这个文件大小为 500 
    那么,我的业务需求可能需要第一次 从 300 位置开始写数据,写到 350 为止。 
    第二次,我又从 50 开始写数据,写到 100 为止。 
    总之,我不是 “一次性” 的 “按顺序” 的将这个文件写完。 
    那么,RandomAccessFile 可以支持这种操作。 
     
     API 
      void setLength(long newLength) 
          Sets the length of this file. (设置文件的预计大小) 
      void seek(long pos) 
          Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs. 
          假设为这个方法传入 1028 这个参数,表示,将从文件的 1028 位置开始写入。 
      void write(byte[] b, int off, int len) 
          Writes len bytes from the specified byte array starting at offset off to this file. 
      write(byte[] b) 
          Writes b.length bytes from the specified byte array to this file, starting at the current file pointer. 
      void writeUTF(String str) 
          Writes a string to the file using modified UTF-8 encoding in a machine-independent manner. 
       String readLine() 
          Reads the next line of text from this file.

实验代码:

Java代码  
  1. public static void main(String[] args) throws Exception {
  2. File file = new File("1.txt");
  3. RandomAccessFile accessFile = new RandomAccessFile(file,"rwd");
  4. /* 设置文件为 3 个字节大小 */
  5. accessFile.setLength(3);
  6. /* 向第二个位置写入 '2' */
  7. accessFile.seek(1);
  8. accessFile.write("2".getBytes());
  9. /* 向第一个位置写入 '1' */
  10. accessFile.seek(0); accessFile.write("1".getBytes());
  11. /* 向第三个位置写入 '3' */
  12. accessFile.seek(2);
  13. accessFile.write("3".getBytes()); accessFile.close();
  14. // 期待文件的内容为 :123
  15. }

以上实验成功,虽然我们写入字符串的顺序为 "2"、"1"、"3",但是因为设置了文件偏移量的关系,文件最终保存的数据为 : 123 
    另一个疑问,写完这三个数据,文件的大小已经为 3 个字节大小了。已经撑满了写入的数据,那么我们继续往里面放数据会有什么效果? 
    
    /* 向超出大小的第四个字节位置写入数据 */ 
    accessFile.seek(3); 
    accessFile.write("400".getBytes()); 
    
    以上代码无论 seek 方法指定的文件指针偏移量以及存入的数据,都已经超出了最开始为文件设定的 3 个字节的大小。 
    按照我的猜测,至少 “accessFile.seek(3)” 位置会抛出 "ArrayIndexOutOfBoundsException" 异常,表示下标越界。 
    而,单独执行 "accessFile.write("400".getBytes())" 应该可以成功。因为这个需求属于合理的,应该有执行它的机制。 
    实验结果是两句代码都是成功的。貌似是说明,文件隐含的大型的字节数组,可以自动撑大。 
    
    但是要注意的问题是,必须要保证所设定的文件大小的每一个位置都具有合法的数据,至少不能为空。 
    例如: 
        /* 向第三个位置写入 '3'  */ 
        accessFile.seek(2); 
        accessFile.write("3".getBytes()); 
        
        accessFile.seek(5); 
        accessFile.write("400".getBytes()); 
    那么结合之前的代码,最后的结果为: 
        123口口400 
    在空白的两个位置处出现了乱码。这是理所应当的。 
    
    另外,假设我们为文件指定了一百个长度: 
        accessFile.setLength(100); 
    而,实际上,我们只为其前五个位置设置了值。那么理所当然的是,文件保存的数据,最后会缀上 95 个乱码。 
    
7. 准备工作应该十分充分了。接下来上代码。

Java代码  
  1. import java.io.File;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.RandomAccessFile;
  5. import java.net.HttpURLConnection;
  6. import java.net.URL;
  7. /**
  8. * 多线程方式文件下载
  9. */
  10. public class MulThreadDownload {
  11. /* 下载的URL */
  12. private URL downloadUrl;
  13. /* 用于保存的本地文件 */
  14. private File localFile;
  15. /* 没条线程下载的数据长度 */
  16. private int block;
  17. public static void main(String[] args) {
  18. /* 可以为网络上任意合法下载地址 */
  19. String downPath = "http://192.168.1.102:8080/myvideoweb/down.avi";
  20. MulThreadDownload threadDownload = new MulThreadDownload();
  21. /* 开 10 条线程下载下载 */
  22. try {
  23. threadDownload.download(downPath, 10);
  24. } catch (Exception e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. /**
  29. * 多线程文件下载
  30. *
  31. * @param path 下载地址
  32. * @param threadCount 线程数
  33. */
  34. public void download(String path, int threadCount) throws Exception {
  35. downloadUrl = new URL(path);
  36. HttpURLConnection conn = (HttpURLConnection) downloadUrl
  37. .openConnection();
  38. /* 设置 GET 请求方式 */
  39. conn.setRequestMethod("GET");
  40. /* 设置响应时间超时为 5 秒 */
  41. conn.setConnectTimeout(5 * 1000);
  42. /* 获取本地文件名 */
  43. String filename = parseFilename(path);
  44. /* 获取下载文件的总大小 */
  45. int dataLen = conn.getContentLength();
  46. if (dataLen < 0) {
  47. System.out.println("获取数据失败");
  48. return;
  49. }
  50. /* 创建本地目标文件,并设置其大小为准备下载文件的总大小 */
  51. localFile = new File(filename);
  52. RandomAccessFile accessFile = new RandomAccessFile(localFile, "rwd");
  53. /* 这时候,其实本地目录下,已经创建好了一个大小为下载文件的总大小的文件 */
  54. accessFile.setLength(dataLen);
  55. accessFile.close();
  56. /* 计算每条线程要下载的数据大小 */
  57. block = dataLen % threadCount == 0 ? dataLen / threadCount : dataLen / threadCount + 1;
  58. /* 启动线程下载文件 */
  59. for (int i = 0; i < threadCount; i++) {
  60. new DownloadThread(i).start();
  61. }
  62. }
  63. /**
  64. * 解析文件
  65. */
  66. private String parseFilename(String path) {
  67. return path.substring(path.lastIndexOf("/") + 1);
  68. }
  69. /**
  70. * 内部类: 文件下载线程类
  71. */
  72. private final class DownloadThread extends Thread {
  73. /* 线程 id */
  74. private int threadid;
  75. /* 开始下载的位置 */
  76. private int startPosition;
  77. /* 结束下载的位置 */
  78. private int endPosition;
  79. /**
  80. * 新建一个下载线程
  81. * @param threadid 线程 id
  82. */
  83. public DownloadThread(int threadid) {
  84. this.threadid = threadid;
  85. startPosition = threadid * block;
  86. endPosition = (threadid + 1) * block - 1;
  87. }
  88. @Override
  89. public void run() {
  90. System.out.println("线程 '" + threadid + "'启动下载..");
  91. RandomAccessFile accessFile = null;
  92. try {
  93. /* 设置从本地文件的什么位置开始写入数据 ,"rwd" 表示对文件具有读写删权限 */
  94. accessFile = new RandomAccessFile(localFile, "rwd");
  95. accessFile.seek(startPosition);
  96. HttpURLConnection conn = (HttpURLConnection) downloadUrl.openConnection();
  97. conn.setRequestMethod("GET");
  98. conn.setReadTimeout(5 * 1000);
  99. /* 为 HTTP 设置 Range 属性,可以指定服务器返回数据的范围 */
  100. conn.setRequestProperty("Range", "bytes=" + startPosition + "-"
  101. + endPosition);
  102. /* 将数据写往本地文件 */
  103. writeTo(accessFile, conn);
  104. System.out.println("线程 '" + threadid + "'完成下载");
  105. } catch (IOException e) {
  106. e.printStackTrace();
  107. } finally {
  108. try {
  109. if(accessFile != null) {
  110. accessFile.close();
  111. }
  112. } catch (IOException ex) {
  113. ex.printStackTrace();
  114. }
  115. }
  116. }
  117. /**
  118. * 将下载数据写往本地文件
  119. */
  120. private void writeTo(RandomAccessFile accessFile,
  121. HttpURLConnection conn){
  122. InputStream is = null;
  123. try {
  124. is = conn.getInputStream();
  125. byte[] buffer = new byte[1024];
  126. int len = -1;
  127. while ((len = is.read(buffer)) != -1) {
  128. accessFile.write(buffer, 0, len);
  129. }
  130. } catch (IOException e) {
  131. e.printStackTrace();
  132. } finally {
  133. try {
  134. if(is != null) {
  135. is.close();
  136. }
  137. } catch (Exception ex) {
  138. ex.printStackTrace();
  139. }
  140. }
  141. }
  142. }
  143. }

转载于:https://www.cnblogs.com/bigben0123/p/4527413.html

通过HTTP协议实现多线程下载相关推荐

  1. 【Android】多线程下载加断点续传

    http://blog.csdn.net/smbroe/article/details/42270573 文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大 ...

  2. Android学习笔记---java实现多线程下载器,30_多线程下载原理介绍和使用

    2013-04-01 30_多线程下载原理 -------------------- a.文件下载原里:   使用http协议实现多线程下载 b.采用多线程下载,可以抢占服务器cpu的处理时间,实现快 ...

  3. [Mac] mac linux 多线程下载利器 axel

    ​> 之前做过一些文件下载的统计,发现谷歌浏览器chrome和火狐firefox, 一般都是单线程的下载文件,360浏览器却是多线程的下载. 如今切换到了mac上,发现没有360哪个浏览器,就像 ...

  4. 基于流式的md5计算-多线程下载工具Lwget介绍

    在数据传输的时候,我们希望实现以下目标: 1. 使用多线程传输,加速下载速度 2. 数据在传输过程中,进行流式md5计算,避免在传输完毕之后校验大文件 3. 支持断点续传 4. 支持http协议和ft ...

  5. 多线程下载的原理和基本用法

    刚学了下多线程的下载,可能是初次接触的原因吧,理解起来觉得稍微有点难.所以想写一篇博客来记录下,加深自己理解的同时,也希望能够帮到一些刚接触的小伙伴.由于涉及到网络的传输,那么就会涉及到http协议. ...

  6. C#实现HTTP协议:多线程文件传输

    很多人都有过使用网络蚂蚁或网络快车互联网文件的经历,这些软件的使用可以大大加速互联网上文件的传输速度,减少文件传输的时间.这些软件为什么有如此大的魔力呢?其主要原因是这些软件都采用了多线程下载和断点续 ...

  7. Android_线程_多线程下载

    多线程下载图示: 代码: import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; impor ...

  8. 【软件分享】免费多线程下载神器,可完全替代IDM(支持MacWindows)

    引言 提到下载软件,大家最常用的可能就是迅雷或者IDM了.笔者一直以来也都是用的IDM,IDM最核心的功能包括:多线程下载.断点续传以及网页资源嗅探等.但IDM是需要收费的,而且不支持Mac.所以今天 ...

  9. neat download manager是什么?多线程下载工具 NeatDownloadManager

    NeatDownloadManager 是一款 Mac 多线程下载应用.支持HTTP/HTTPS/FTP协议.也可以使用 HTTP 代理和 SOCKS 协议.支持断点续传,可下载 HLS 视频的所有 ...

最新文章

  1. Spring boot使用Spring Security和OAuth2保护REST接口
  2. linq lambda 分组后排序
  3. MYSQL WHERE 当一个字段不为NULL的时候使用另一个字段判断
  4. SmartPointer
  5. 利用SecureCRT上传、下载文件(使用sz与rz命令)
  6. boost::intrusive::pack_options用法的测试程序
  7. python文本解析_Python之文本文件解析
  8. pimg src=http://img.blog.csdn.net/20150823142545135?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQ...
  9. 给我的宏基上网本用U盘装XP系统
  10. 截获android屏幕服务,如何捕获android设备屏幕内容?
  11. Softmax loss, softmax, multinominal and logistic loss
  12. 云服务器可以文件服务器,云服务器 可以上传文件
  13. C语言开发笔记(六)实参和形参
  14. ADempiere3.6.0LTS - 创建国家地区城市(基于Ubuntu Desktop 12.04 LTS)
  15. Bailian2721 忽略大小写比较字符串大小(POJ NOI0107-16)【字符串】
  16. 色彩原理与色彩搭配知识点回顾总结
  17. C虾仔笔记 - ScrollView垂向滚动视图
  18. H7-TOOL的WiFi版基本成形,无线烧录,无线RTT,无线串口,无线CAN/CANFD,无线LUA小程序,无线示波器等,且支持局域网和外网
  19. Codeforces Round #460 (Div. 2) C Seat Arrangements
  20. 优矿量化向导式因子选股

热门文章

  1. Pixhawk之姿态解算篇(2)_mahony算法分析
  2. 《Linux内核设计与实现》读书笔记(六)- 内核数据结构
  3. LeetCode每日一题 52. N皇后 II
  4. linux usb视频开发板,ARM开发板上USB 摄像头图像采集实现
  5. mysql中的参数如何调试_mysql 查询优化 ~ 查询参数调节
  6. Linux线程退出、资源回收、资源清理的方法
  7. Pwn环境配置(二)——VMware虚拟机安装Ubuntu 16.04系统
  8. php任务队列有什么优势,PHP使用任务队列这个词的含义,到底什么是任务队列
  9. 交叉驰豫的影响因素_交叉滚子轴承系列吉林薄壁交叉滚子轴承用途博盈
  10. 用Artifactory管理内部Maven仓库