这段时间研究java的io与nio框架,一时兴起决定用java实现一个下载工具,主要有下面几个功能

1)支持多任务下载

2)支持多线程下载

3) 支持断点续传

4)错误线程任务的重新调度

用到的技术点

1) http协议的range头技术

2)java的多线程

3)java的网络编程,主要是HttpUrlConnection类

4)java的io文件操作, 如RandomAccessFile类

速度还不错,基本上和浏览器下载差不多,可能比它还要快,我只开了10个线程。

  1. package org.blackfoxer.cat;

  2. import java.io.File;

  3. import java.io.IOException;

  4. import java.io.RandomAccessFile;

  5. import java.io.UnsupportedEncodingException;

  6. import java.net.HttpURLConnection;

  7. import java.net.URL;

  8. import java.net.URLDecoder;

  9. import java.text.DecimalFormat;

  10. import java.util.concurrent.CountDownLatch;

  11. import java.util.concurrent.TimeUnit;

  12. import java.util.regex.Matcher;

  13. import java.util.regex.Pattern;

  14. public class Job {

  15. private int fileSize;

  16. private String fileName;

  17. private int connectTimeout = 10000;

  18. private int readTimeout = 20000;

  19. private String url;

  20. private String storeDir;

  21. private int taskNum;

  22. private String jobId;

  23. private int[] startIndexes;

  24. private int[] endIndexes;

  25. private int[] progress;

  26. private Task[] tasks;

  27. private File storeDirFile;

  28. private File dtDirFile;

  29. private File localFile;

  30. private ThreadLocal<RandomAccessFile> rafLocalTl;

  31. private ThreadLocal<RandomAccessFile> rafOffsetTl;

  32. private CountDownLatch latch;

  33. private ProgressThread pt;

  34. public Job(String url, String storeDir, int taskNum) throws IOException {

  35. this.url = url;

  36. this.storeDir = storeDir;

  37. this.taskNum = taskNum;

  38. this.startIndexes = new int[taskNum];

  39. this.endIndexes = new int[taskNum];

  40. this.progress = new int[taskNum];

  41. this.tasks = new Task[taskNum];

  42. this.latch = new CountDownLatch(taskNum);

  43. this.jobId = Math.abs(url.hashCode()) + "_" + taskNum;

  44. this.rafLocalTl = new ThreadLocal<RandomAccessFile>();

  45. this.rafOffsetTl = new ThreadLocal<RandomAccessFile>();

  46. this.pt = new ProgressThread();

  47. }

  48. public void startJob() throws Exception {

  49. long start = System.currentTimeMillis();

  50. System.out.println("开始下载文件...");

  51. boolean j = fetchFileMetaInfo();

  52. if (j) {

  53. assignTasks();

  54. createFiles();

  55. startTasks();

  56. openProgressThread();

  57. waitForCompeletion();

  58. long end = System.currentTimeMillis();

  59. System.out.println("下载完毕,全程耗时" + (end - start) + "ms");

  60. } else {

  61. System.out.println("获取文件长度或文件名失败,请重试");

  62. }

  63. }

  64. private void openProgressThread() {

  65. this.pt.start();

  66. }

  67. private void waitForCompeletion() throws Exception {

  68. latch.await();

  69. deleteFiles();

  70. pt.join();

  71. }

  72. private void deleteFiles() {

  73. if (dtDirFile != null) {

  74. File[] subFiles = dtDirFile.listFiles();

  75. for (File subFile : subFiles) {

  76. subFile.delete();

  77. }

  78. dtDirFile.delete();

  79. }

  80. }

  81. // 1.fetch file size and file name

  82. private boolean fetchFileMetaInfo() throws IOException {

  83. HttpURLConnection connection = createConnection();

  84. connection.setRequestMethod("GET");

  85. if (connection.getResponseCode() == 200) {

  86. this.fileSize = connection.getContentLength();

  87. String disposition = connection.getHeaderField("Content-Disposition");

  88. if (disposition == null) {

  89. parseFileNameFromUrl(url);

  90. } else {

  91. parseFileNameFromDisposition(disposition);

  92. }

  93. if (this.fileName == null || this.fileSize < 0) {

  94. return false;

  95. }

  96. System.out.println("找到文件资源,长度为" + fileSize + ",资源名称为" + fileName);

  97. return true;

  98. }

  99. return false;

  100. }

  101. private void parseFileNameFromUrl(String url) throws UnsupportedEncodingException {

  102. this.fileName = url.substring(url.lastIndexOf("/") + 1, url.length());

  103. if (this.fileName.contains("%")) {

  104. this.fileName = URLDecoder.decode(this.fileName, "UTF-8");

  105. }

  106. }

  107. private void parseFileNameFromDisposition(String disposition) throws UnsupportedEncodingException {

  108. Pattern pattern = Pattern.compile(".+filename=\"(.+?)\".*");

  109. Matcher matcher = pattern.matcher(disposition);

  110. if (matcher.matches()) {

  111. this.fileName = new String(matcher.group(1).getBytes("ISO-8859-1"), "UTF-8");

  112. } else {

  113. parseFileNameFromUrl(url);

  114. }

  115. }

  116. public HttpURLConnection createConnection() throws IOException {

  117. URL urlObj = new URL(url);

  118. HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();

  119. connection.setConnectTimeout(connectTimeout);

  120. connection.setReadTimeout(readTimeout);

  121. connection.setRequestProperty("Accept-Charset", "UTF-8");

  122. connection.setRequestProperty("contentType", "UTF-8");

  123. return connection;

  124. }

  125. // 2.assign every task start index and end index out of the file

  126. private void assignTasks() throws IOException {

  127. for (int i = 0; i < taskNum; i++) {

  128. int size = fileSize / taskNum;

  129. int startIndex = i * size;

  130. int endIndex = i == taskNum - 1 ? fileSize - 1 : i * size + size - 1;

  131. this.startIndexes[i] = startIndex;

  132. this.endIndexes[i] = endIndex;

  133. }

  134. }

  135. // 3.create the local file and temp directory

  136. private void createFiles() throws IOException {

  137. storeDirFile = new File(storeDir);

  138. storeDirFile.mkdirs();

  139. localFile = new File(storeDirFile, fileName);

  140. dtDirFile = new File(storeDirFile, "." + jobId);

  141. dtDirFile.mkdirs();

  142. if (!localFile.exists()) {

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

  144. raf.setLength(fileSize);

  145. raf.close();

  146. }

  147. }

  148. // 4.let the task start to do their work

  149. private void startTasks() throws IOException {

  150. for (int i = 0; i < taskNum; i++) {

  151. Task task = new Task(this, i);

  152. tasks[i] = task;

  153. task.start();

  154. }

  155. }

  156. private int totalReadBytes() {

  157. int totalReadBytes = 0;

  158. for (int i = 0; i < progress.length; i++) {

  159. totalReadBytes += progress[i];

  160. }

  161. return totalReadBytes;

  162. }

  163. public int[] getStartIndexes() {

  164. return startIndexes;

  165. }

  166. public int[] getEndIndexes() {

  167. return endIndexes;

  168. }

  169. public void writeLocalFile(int startIndex, byte[] buf, int off, int len) throws IOException {

  170. if (rafLocalTl.get() == null) {

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

  172. rafLocalTl.set(raf);

  173. }

  174. RandomAccessFile raf = rafLocalTl.get();

  175. raf.seek(startIndex);

  176. raf.write(buf, off, len);

  177. }

  178. // 5.let task to report their progress

  179. public void reportProgress(int index, int readBytes) {

  180. progress[index] = readBytes;

  181. }

  182. public void closeTaskResource(int index) throws IOException {

  183. RandomAccessFile raf = rafLocalTl.get();

  184. if (raf != null) {

  185. raf.close();

  186. }

  187. raf = rafOffsetTl.get();

  188. if (raf != null) {

  189. raf.close();

  190. }

  191. }

  192. public void commitOffset(int index, int offset) throws IOException {

  193. File offsetFile = new File(dtDirFile, String.valueOf(index));

  194. if (rafOffsetTl.get() == null) {

  195. RandomAccessFile raf = new RandomAccessFile(offsetFile, "rw");

  196. rafOffsetTl.set(raf);

  197. }

  198. RandomAccessFile raf = rafOffsetTl.get();

  199. raf.seek(0);

  200. raf.writeInt(offset);

  201. }

  202. public int readOffset(int index) throws IOException {

  203. File offsetFile = new File(dtDirFile, String.valueOf(index));

  204. if (offsetFile.exists()) {

  205. RandomAccessFile raf = new RandomAccessFile(offsetFile, "rw");

  206. raf.seek(0);

  207. int offset = raf.readInt();

  208. raf.close();

  209. return offset;

  210. }

  211. return 0;

  212. }

  213. public void reStartTask(int index) throws IOException {

  214. Task task = new Task(this, index);

  215. tasks[index] = task;

  216. task.start();

  217. System.out.println("任务" + index + "发生错误,重新调度该任务");

  218. }

  219. public void taskFinished() {

  220. latch.countDown();

  221. }

  222. private class ProgressThread extends Thread {

  223. private DecimalFormat decimalFormat = new DecimalFormat();

  224. public void run() {

  225. decimalFormat.applyPattern("0.0");

  226. while (true) {

  227. try {

  228. int endPointX = totalReadBytes();

  229. TimeUnit.SECONDS.sleep(1);

  230. int endPointY = totalReadBytes();

  231. int waitSeconds = 1;

  232. while (endPointY - endPointX == 0) {

  233. TimeUnit.SECONDS.sleep(1);

  234. waitSeconds++;

  235. endPointY = totalReadBytes();

  236. }

  237. int speed = (endPointY - endPointX) / waitSeconds;

  238. String speedStr = speed > 1024 ? speed/1024+"kb/s":speed+"b/s";

  239. String percent = decimalFormat.format(endPointY * 100.0 / fileSize);

  240. int remainSeconds = (fileSize - endPointY)/speed;

  241. System.out.println("下载完成"+percent+"%,速度"+speedStr+",估计还需要"+remainSeconds+"秒");

  242. if("100.0".equals(percent)) {

  243. break;

  244. }

  245. } catch (InterruptedException e) {

  246. e.printStackTrace();

  247. }

  248. }

  249. }

  250. }

  251. }

  1. package org.blackfoxer.cat;

  2. import java.io.IOException;

  3. import java.io.InputStream;

  4. import java.net.HttpURLConnection;

  5. public class Task extends Thread {

  6. private Job owner;

  7. private int index;

  8. private int readBytes;

  9. private int startIndex;

  10. private int endIndex;

  11. public Task(Job owner,int index) throws IOException {

  12. this.owner = owner;

  13. this.index = index;

  14. if(owner.readOffset(index)!=0) {

  15. this.readBytes = owner.readOffset(index)-owner.getStartIndexes()[index];

  16. owner.reportProgress(index, readBytes);

  17. }

  18. this.startIndex = owner.getStartIndexes()[index]+readBytes;

  19. this.endIndex = owner.getEndIndexes()[index];

  20. }

  21. public void run() {

  22. InputStream inputStream = null;

  23. HttpURLConnection connection = null;

  24. try {

  25. if(startIndex > endIndex) {

  26. owner.taskFinished();

  27. return;

  28. }

  29. connection = owner.createConnection();

  30. connection.setRequestMethod("GET");

  31. String range = "bytes="+startIndex+"-"+endIndex ;

  32. connection.setRequestProperty("Range", range);

  33. if(connection.getResponseCode()==206) {

  34. inputStream = connection.getInputStream();

  35. int len = -1;

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

  37. int offset = startIndex;

  38. while((len=inputStream.read(buf))!=-1) {

  39. owner.writeLocalFile(offset,buf,0,len);

  40. readBytes+=len;

  41. offset+=len;

  42. owner.commitOffset(index,offset);

  43. owner.reportProgress(index,readBytes);

  44. }

  45. owner.taskFinished();

  46. }

  47. } catch (IOException e) {

  48. e.printStackTrace();

  49. try {

  50. owner.reStartTask(index);

  51. } catch (IOException e1) {

  52. e1.printStackTrace();

  53. }

  54. } finally {

  55. if(inputStream != null) {

  56. try {

  57. inputStream.close();

  58. } catch (IOException e) {

  59. e.printStackTrace();

  60. }

  61. }

  62. if(connection != null) {

  63. connection.disconnect();

  64. }

  65. try {

  66. owner.closeTaskResource(index);

  67. } catch (IOException e) {

  68. e.printStackTrace();

  69. }

  70. }

  71. }

  72. }

  1. package org.blackfoxer.cat;

  2. import java.io.BufferedReader;

  3. import java.io.IOException;

  4. import java.io.InputStreamReader;

  5. public class JavaXunlei {

  6. private static final BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

  7. private static String storeDir = null;

  8. public static void main(String args[]) throws Exception {

  9. storeDir = getInput("请先设置你的文件存储目录:");

  10. int taskNum = getIntInput("请输入你的开启下载的线程数:");

  11. while(true) {

  12. String url = getInput("请输入文件链接地址:");

  13. Job job = new Job(url,storeDir,taskNum);

  14. job.startJob();

  15. }

  16. }

  17. private static int getIntInput(String message) throws IOException {

  18. String number = getInput(message);

  19. while(!number.matches("\\d+")) {

  20. System.out.println("线程数必须是1个整数");

  21. number = getInput(message);

  22. }

  23. return Integer.parseInt(number);

  24. }

  25. private static String getInput(String message) throws IOException {

  26. System.out.print(message);

  27. String line = in.readLine();

  28. while(line == null || line.trim().length()<1) {

  29. System.out.print(message);

  30. line = in.readLine();

  31. }

  32. return line.trim();

  33. }

  34. }

java版迅雷下载源码分享相关推荐

  1. java版坦克大战源码分享

    前言 利用摸鱼时间学了个java的游戏引擎FXGL 本游戏是基于jdk17和FXGL开发的 操作 按键 功能 WSAD 移动 空格 发射子弹 E 使用技能 道具说明 道具 玩家吃到 敌人吃到 星星 子 ...

  2. 插件一:JAVA微信砍价活动源码分享[商品帮砍到0元,免费领取奖品]

    插件一:微信砍价活动源码分享 [商品帮砍到0元,免费领取奖品] 活动描述: 砍价活动即公众号向粉丝推广的0价赠商品(或优惠价购商品)活动,用户通过分享好友帮其砍价,可将价格从原价一路砍到底价,并抢得名 ...

  3. java版苹果免签源码超级签名免签源码

    介绍: 苹果超级签名是什么我就不在赘述了,项目由java+vue开发 功能: 1.支持登录注册,拥有共有池,可上传证书等基本操作,支持用户自行上传证书 2.支持修改分发页面轮播图,简介,安卓合并 3. ...

  4. Java版赤色要塞源码分析

    1.框架与环境搭建 1.1 本游戏使用了以下框架 [url=http://slick.ninjacave.com/]slick2d[/url] [url=http://www.lwjgl.org/]l ...

  5. java版聚合支付源码Spring Cloud+Spring Boot+mybatis+security+uniapp+Redis+MQ+VR全景+b2b2c多商家入驻前后端分离商城源码

    @源码地址来源: https://minglisoft.cn/honghu/business.html 电商微信支付.支付宝支付.余额支付代码 package com.honghu.cloud.con ...

  6. Modbus通信协议+Modbus串口调试工具+Java版协议解析源码

    网络与串口二合一调试助手TCPCOM: https://download.csdn.net/download/liuyuan_java/87454762 Modbus调试工具,模拟串口调试工具 htt ...

  7. Java版知识付费源码 Spring Cloud+Spring Boot+Mybatis+uniapp+前后端分离实现知识付费平台

    提供职业教育.企业培训.知识付费系统搭建服务.系统功能包含:录播课.直播课.题库.营销.公司组织架构.员工入职培训等. 提供私有化部署,免费售后,专业技术指导,支持PC.APP.H5.小程序多终端同步 ...

  8. JAVA版B2B2C商城源码 拼团商城 分销商城 springboot商城多商家入驻商城系统

    @源码地址来源: https://minglisoft.cn/honghu2/business.html 直播带货源码: /*** Copyright © 2012-2017 <a href=& ...

  9. JAVA版B2B2C商城源码多商户入驻商城

    三勾商城多商户是开发友好的微信小程序商城,框架支持SAAS,支持发布 iOS + Android + 公众号 + H5 + 各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)等多个平台,不可多得 ...

最新文章

  1. [JAVAEE] Thymeleaf 基本语法:常用表达式
  2. python 列表筛选数据
  3. 【Android】Mac Android adb 配置
  4. bom event周期_DOM-BOM-EVENT(1)
  5. 《朝花夕拾》金句摘抄(二)
  6. 个人计算机有控制器和运算器吗,cpu是由控制器和运算器组成的对还是错
  7. python中的event_Python event
  8. 启动另一个activity
  9. android开发常用技术,[转载]Android开发常用调试技术记录
  10. java中bash应用_在bash脚本中查找java应用程序的进程ID(以查看目标应用程序是否已在运行)...
  11. 【ZOJ 4070】Function and Function
  12. 编译aspell时出错
  13. 晶振封装与频率对照表
  14. c语言判断二次函数,知识:六法搞定二次函数解析式的确定
  15. 嵌入式系统开发笔记17:CJ/T-188 冷热量表协议解析6
  16. 计算机辅助翻译专业实训报告,计算机辅助翻译实训报告格式(7页)-原创力文档...
  17. 网站微调是什么意思?能带来什么作用?
  18. ubuntu NFS SCP SFTP
  19. 浅析语音对讲功能在车载监控系统中的应用意义
  20. 基于DDD的现代ASP.NET开发框架--ABP系列文章总目录

热门文章

  1. 搭建前端组件库(二)
  2. Java通过freemarker生成pdf文件并盖章
  3. C语言数据结构篇——双链表的创建,插入,节点删除,打印等操作
  4. 2021年3季度手机银行活跃用户突破6.2亿,中小上市银行增长突出
  5. chrome浏览器的JSON格式化插件JSON-Handle
  6. android上传Jcenter
  7. js调用php和php调用js的方法
  8. php调用c++程序
  9. js给input控件添加onkeypress属性
  10. 重启d3d11篇02_初始化d3d11