Java / Android 基于Http的多线程下载的实现

先说下原理,原理明白了,其实很简单:

a、对于网络上的一个资源,首先发送一个请求,从返回的Content-Length中回去需要下载文件的大小,然后根据文件大小创建一个文件。

 

 
  1. this.fileSize = conn.getContentLength();// 根据响应获取文件大小

  2. File dir = new File(dirStr);

  3. this.localFile = new File(dir, filename);

  4. RandomAccessFile raf = new RandomAccessFile(this.localFile, "rw");

  5. raf.setLength(fileSize);

  6. raf.close();

 

b、根据线程数和文件大小,为每个线程分配下载的字节区间,然后每个线程向服务器发送请求,获取这段字节区间的文件内容。

 

 
  1. conn.setRequestProperty("Range", "bytes=" + startPos + "-"

  2. + endPos);// 设置获取实体数据的范围

 

c、利用RandomAccessFile的seek方法,多线程同时往一个文件中写入字节。

 

 
  1. raf.seek(startPos);

  2. while ((len = is.read(buf)) != -1)

  3. {

  4. raf.write(buf, 0, len);

  5. }

分析完了原理就很简单了,我封装了一个类,利用这个类的实例进行下载,所需参数:下载资源的URI, 本地文件路径,线程的数量。

 

 

 
  1. package com.zhy.mutilthread_download;

  2.  
  3. import java.io.File;

  4. import java.io.IOException;

  5. import java.io.InputStream;

  6. import java.io.RandomAccessFile;

  7. import java.net.HttpURLConnection;

  8. import java.net.URL;

  9.  
  10. public class MultipartThreadDownloador

  11. {

  12.  
  13. /**

  14. * 需要下载资源的地址

  15. */

  16. private String urlStr;

  17. /**

  18. * 下载的文件

  19. */

  20. private File localFile;

  21. /**

  22. * 需要下载文件的存放的本地文件夹路径

  23. */

  24. private String dirStr;

  25. /**

  26. * 存储到本地的文件名

  27. */

  28. private String filename;

  29.  
  30. /**

  31. * 开启的线程数量

  32. */

  33. private int threadCount;

  34. /**

  35. * 下载文件的大小

  36. */

  37. private long fileSize;

  38.  
  39. public MultipartThreadDownloador(String urlStr, String dirStr,

  40. String filename, int threadCount)

  41. {

  42. this.urlStr = urlStr;

  43. this.dirStr = dirStr;

  44. this.filename = filename;

  45. this.threadCount = threadCount;

  46. }

  47.  
  48. public void download() throws IOException

  49. {

  50. createFileByUrl();

  51.  
  52. /**

  53. * 计算每个线程需要下载的数据长度

  54. */

  55. long block = fileSize % threadCount == 0 ? fileSize / threadCount

  56. : fileSize / threadCount + 1;

  57.  
  58. for (int i = 0; i < threadCount; i++)

  59. {

  60. long start = i * block;

  61. long end = start + block >= fileSize ? fileSize : start + block - 1;

  62.  
  63. new DownloadThread(new URL(urlStr), localFile, start, end).start();

  64. }

  65.  
  66. }

  67.  
  68. /**

  69. * 根据资源的URL获取资源的大小,以及在本地创建文件

  70. */

  71. public void createFileByUrl() throws IOException

  72. {

  73. URL url = new URL(urlStr);

  74. HttpURLConnection conn = (HttpURLConnection) url.openConnection();

  75. conn.setConnectTimeout(15 * 1000);

  76. conn.setRequestMethod("GET");

  77. conn.setRequestProperty(

  78. "Accept",

  79. "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");

  80. conn.setRequestProperty("Accept-Language", "zh-CN");

  81. conn.setRequestProperty("Referer", urlStr);

  82. conn.setRequestProperty("Charset", "UTF-8");

  83. conn.setRequestProperty(

  84. "User-Agent",

  85. "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");

  86. conn.setRequestProperty("Connection", "Keep-Alive");

  87. conn.connect();

  88.  
  89. if (conn.getResponseCode() == 200)

  90. {

  91. this.fileSize = conn.getContentLength();// 根据响应获取文件大小

  92. if (fileSize <= 0)

  93. throw new RuntimeException(

  94. "the file that you download has a wrong size ... ");

  95. File dir = new File(dirStr);

  96. if (!dir.exists())

  97. dir.mkdirs();

  98. this.localFile = new File(dir, filename);

  99. RandomAccessFile raf = new RandomAccessFile(this.localFile, "rw");

  100. raf.setLength(fileSize);

  101. raf.close();

  102.  
  103. System.out.println("需要下载的文件大小为 :" + this.fileSize + " , 存储位置为: "

  104. + dirStr + "/" + filename);

  105.  
  106. } else

  107. {

  108. throw new RuntimeException("url that you conneted has error ...");

  109. }

  110. }

  111.  
  112. private class DownloadThread extends Thread

  113. {

  114. /**

  115. * 下载文件的URI

  116. */

  117. private URL url;

  118. /**

  119. * 存的本地路径

  120. */

  121. private File localFile;

  122. /**

  123. * 是否结束

  124. */

  125. private boolean isFinish;

  126. /**

  127. * 开始的位置

  128. */

  129. private Long startPos;

  130. /**

  131. * 结束位置

  132. */

  133. private Long endPos;

  134.  
  135. public DownloadThread(URL url, File savefile, Long startPos, Long endPos)

  136. {

  137. this.url = url;

  138. this.localFile = savefile;

  139. this.startPos = startPos;

  140. this.endPos = endPos;

  141. }

  142.  
  143. @Override

  144. public void run()

  145. {

  146. System.out.println(Thread.currentThread().getName() + "开始下载...");

  147. try

  148. {

  149. HttpURLConnection conn = (HttpURLConnection) url

  150. .openConnection();

  151. conn.setConnectTimeout(15 * 1000);

  152. conn.setRequestMethod("GET");

  153. conn.setRequestProperty(

  154. "Accept",

  155. "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");

  156. conn.setRequestProperty("Accept-Language", "zh-CN");

  157. conn.setRequestProperty("Referer", url.toString());

  158. conn.setRequestProperty("Charset", "UTF-8");

  159. conn.setRequestProperty("Range", "bytes=" + startPos + "-"

  160. + endPos);// 设置获取实体数据的范围

  161.  
  162. conn.setRequestProperty(

  163. "User-Agent",

  164. "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");

  165. conn.setRequestProperty("Connection", "Keep-Alive");

  166. conn.connect();

  167.  
  168. /**

  169. * 代表服务器已经成功处理了部分GET请求

  170. */

  171. if (conn.getResponseCode() == 206)

  172. {

  173. InputStream is = conn.getInputStream();

  174. int len = 0;

  175. byte[] buf = new byte[1024];

  176.  
  177. RandomAccessFile raf = new RandomAccessFile(localFile,

  178. "rwd");

  179. raf.seek(startPos);

  180. while ((len = is.read(buf)) != -1)

  181. {

  182. raf.write(buf, 0, len);

  183. }

  184. raf.close();

  185. is.close();

  186. System.out.println(Thread.currentThread().getName()

  187. + "完成下载 : " + startPos + " -- " + endPos);

  188. this.isFinish = true;

  189. } else

  190. {

  191. throw new RuntimeException(

  192. "url that you conneted has error ...");

  193. }

  194. } catch (IOException e)

  195. {

  196. e.printStackTrace();

  197. }

  198. }

  199.  
  200. }

  201.  
  202.  
  203.  
  204. }


createFileByUrl方法,就是我们上述的原理的步骤1,得到文件大小和创建本地文件。我在程序使用了一个内部类DownloadThread继承Thread,专门负责下载。download()方法,根据线程数量和文件大小计算每个线程需要下载的字节区间,然后开启线程去下载。

 

服务器端:我就扔了几个文件在Tomcat根目录做实验,下面是测试代码:

 

 
  1. package com.zhy.mutilthread_download;

  2.  
  3. import java.io.IOException;

  4.  
  5. public class Test

  6. {

  7.  
  8. public static void main(String[] args)

  9. {

  10. try

  11. {

  12. new MultipartThreadDownloador("http://localhost:8080/nexus.zip",

  13. "f:/backup/nexus", "nexus.zip", 2).download();

  14. } catch (IOException e)

  15. {

  16. e.printStackTrace();

  17. }

  18.  
  19. }

  20. }


输出结果:

 

 

 
  1. 需要下载的文件大小为 :31143237 , 存储位置为: f:/backup/nexus/nexus.zip

  2. Thread-1开始下载...

  3. Thread-2开始下载...

  4. Thread-3开始下载...

  5. Thread-4开始下载...

  6. Thread-4完成下载 : 23357430 -- 31143237

  7. Thread-2完成下载 : 7785810 -- 15571619

  8. Thread-1完成下载 : 0 -- 7785809

  9. Thread-3完成下载 : 15571620 -- 23357429


截图:

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值