java版迅雷下载源码分享
这段时间研究java的io与nio框架,一时兴起决定用java实现一个下载工具,主要有下面几个功能
1)支持多任务下载
2)支持多线程下载
3) 支持断点续传
4)错误线程任务的重新调度
用到的技术点
1) http协议的range头技术
2)java的多线程
3)java的网络编程,主要是HttpUrlConnection类
4)java的io文件操作, 如RandomAccessFile类
速度还不错,基本上和浏览器下载差不多,可能比它还要快,我只开了10个线程。
package org.blackfoxer.cat;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.text.DecimalFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Job {
private int fileSize;
private String fileName;
private int connectTimeout = 10000;
private int readTimeout = 20000;
private String url;
private String storeDir;
private int taskNum;
private String jobId;
private int[] startIndexes;
private int[] endIndexes;
private int[] progress;
private Task[] tasks;
private File storeDirFile;
private File dtDirFile;
private File localFile;
private ThreadLocal<RandomAccessFile> rafLocalTl;
private ThreadLocal<RandomAccessFile> rafOffsetTl;
private CountDownLatch latch;
private ProgressThread pt;
public Job(String url, String storeDir, int taskNum) throws IOException {
this.url = url;
this.storeDir = storeDir;
this.taskNum = taskNum;
this.startIndexes = new int[taskNum];
this.endIndexes = new int[taskNum];
this.progress = new int[taskNum];
this.tasks = new Task[taskNum];
this.latch = new CountDownLatch(taskNum);
this.jobId = Math.abs(url.hashCode()) + "_" + taskNum;
this.rafLocalTl = new ThreadLocal<RandomAccessFile>();
this.rafOffsetTl = new ThreadLocal<RandomAccessFile>();
this.pt = new ProgressThread();
}
public void startJob() throws Exception {
long start = System.currentTimeMillis();
System.out.println("开始下载文件...");
boolean j = fetchFileMetaInfo();
if (j) {
assignTasks();
createFiles();
startTasks();
openProgressThread();
waitForCompeletion();
long end = System.currentTimeMillis();
System.out.println("下载完毕,全程耗时" + (end - start) + "ms");
} else {
System.out.println("获取文件长度或文件名失败,请重试");
}
}
private void openProgressThread() {
this.pt.start();
}
private void waitForCompeletion() throws Exception {
latch.await();
deleteFiles();
pt.join();
}
private void deleteFiles() {
if (dtDirFile != null) {
File[] subFiles = dtDirFile.listFiles();
for (File subFile : subFiles) {
subFile.delete();
}
dtDirFile.delete();
}
}
// 1.fetch file size and file name
private boolean fetchFileMetaInfo() throws IOException {
HttpURLConnection connection = createConnection();
connection.setRequestMethod("GET");
if (connection.getResponseCode() == 200) {
this.fileSize = connection.getContentLength();
String disposition = connection.getHeaderField("Content-Disposition");
if (disposition == null) {
parseFileNameFromUrl(url);
} else {
parseFileNameFromDisposition(disposition);
}
if (this.fileName == null || this.fileSize < 0) {
return false;
}
System.out.println("找到文件资源,长度为" + fileSize + ",资源名称为" + fileName);
return true;
}
return false;
}
private void parseFileNameFromUrl(String url) throws UnsupportedEncodingException {
this.fileName = url.substring(url.lastIndexOf("/") + 1, url.length());
if (this.fileName.contains("%")) {
this.fileName = URLDecoder.decode(this.fileName, "UTF-8");
}
}
private void parseFileNameFromDisposition(String disposition) throws UnsupportedEncodingException {
Pattern pattern = Pattern.compile(".+filename=\"(.+?)\".*");
Matcher matcher = pattern.matcher(disposition);
if (matcher.matches()) {
this.fileName = new String(matcher.group(1).getBytes("ISO-8859-1"), "UTF-8");
} else {
parseFileNameFromUrl(url);
}
}
public HttpURLConnection createConnection() throws IOException {
URL urlObj = new URL(url);
HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
connection.setConnectTimeout(connectTimeout);
connection.setReadTimeout(readTimeout);
connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setRequestProperty("contentType", "UTF-8");
return connection;
}
// 2.assign every task start index and end index out of the file
private void assignTasks() throws IOException {
for (int i = 0; i < taskNum; i++) {
int size = fileSize / taskNum;
int startIndex = i * size;
int endIndex = i == taskNum - 1 ? fileSize - 1 : i * size + size - 1;
this.startIndexes[i] = startIndex;
this.endIndexes[i] = endIndex;
}
}
// 3.create the local file and temp directory
private void createFiles() throws IOException {
storeDirFile = new File(storeDir);
storeDirFile.mkdirs();
localFile = new File(storeDirFile, fileName);
dtDirFile = new File(storeDirFile, "." + jobId);
dtDirFile.mkdirs();
if (!localFile.exists()) {
RandomAccessFile raf = new RandomAccessFile(localFile, "rw");
raf.setLength(fileSize);
raf.close();
}
}
// 4.let the task start to do their work
private void startTasks() throws IOException {
for (int i = 0; i < taskNum; i++) {
Task task = new Task(this, i);
tasks[i] = task;
task.start();
}
}
private int totalReadBytes() {
int totalReadBytes = 0;
for (int i = 0; i < progress.length; i++) {
totalReadBytes += progress[i];
}
return totalReadBytes;
}
public int[] getStartIndexes() {
return startIndexes;
}
public int[] getEndIndexes() {
return endIndexes;
}
public void writeLocalFile(int startIndex, byte[] buf, int off, int len) throws IOException {
if (rafLocalTl.get() == null) {
RandomAccessFile raf = new RandomAccessFile(localFile, "rw");
rafLocalTl.set(raf);
}
RandomAccessFile raf = rafLocalTl.get();
raf.seek(startIndex);
raf.write(buf, off, len);
}
// 5.let task to report their progress
public void reportProgress(int index, int readBytes) {
progress[index] = readBytes;
}
public void closeTaskResource(int index) throws IOException {
RandomAccessFile raf = rafLocalTl.get();
if (raf != null) {
raf.close();
}
raf = rafOffsetTl.get();
if (raf != null) {
raf.close();
}
}
public void commitOffset(int index, int offset) throws IOException {
File offsetFile = new File(dtDirFile, String.valueOf(index));
if (rafOffsetTl.get() == null) {
RandomAccessFile raf = new RandomAccessFile(offsetFile, "rw");
rafOffsetTl.set(raf);
}
RandomAccessFile raf = rafOffsetTl.get();
raf.seek(0);
raf.writeInt(offset);
}
public int readOffset(int index) throws IOException {
File offsetFile = new File(dtDirFile, String.valueOf(index));
if (offsetFile.exists()) {
RandomAccessFile raf = new RandomAccessFile(offsetFile, "rw");
raf.seek(0);
int offset = raf.readInt();
raf.close();
return offset;
}
return 0;
}
public void reStartTask(int index) throws IOException {
Task task = new Task(this, index);
tasks[index] = task;
task.start();
System.out.println("任务" + index + "发生错误,重新调度该任务");
}
public void taskFinished() {
latch.countDown();
}
private class ProgressThread extends Thread {
private DecimalFormat decimalFormat = new DecimalFormat();
public void run() {
decimalFormat.applyPattern("0.0");
while (true) {
try {
int endPointX = totalReadBytes();
TimeUnit.SECONDS.sleep(1);
int endPointY = totalReadBytes();
int waitSeconds = 1;
while (endPointY - endPointX == 0) {
TimeUnit.SECONDS.sleep(1);
waitSeconds++;
endPointY = totalReadBytes();
}
int speed = (endPointY - endPointX) / waitSeconds;
String speedStr = speed > 1024 ? speed/1024+"kb/s":speed+"b/s";
String percent = decimalFormat.format(endPointY * 100.0 / fileSize);
int remainSeconds = (fileSize - endPointY)/speed;
System.out.println("下载完成"+percent+"%,速度"+speedStr+",估计还需要"+remainSeconds+"秒");
if("100.0".equals(percent)) {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package org.blackfoxer.cat;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
public class Task extends Thread {
private Job owner;
private int index;
private int readBytes;
private int startIndex;
private int endIndex;
public Task(Job owner,int index) throws IOException {
this.owner = owner;
this.index = index;
if(owner.readOffset(index)!=0) {
this.readBytes = owner.readOffset(index)-owner.getStartIndexes()[index];
owner.reportProgress(index, readBytes);
}
this.startIndex = owner.getStartIndexes()[index]+readBytes;
this.endIndex = owner.getEndIndexes()[index];
}
public void run() {
InputStream inputStream = null;
HttpURLConnection connection = null;
try {
if(startIndex > endIndex) {
owner.taskFinished();
return;
}
connection = owner.createConnection();
connection.setRequestMethod("GET");
String range = "bytes="+startIndex+"-"+endIndex ;
connection.setRequestProperty("Range", range);
if(connection.getResponseCode()==206) {
inputStream = connection.getInputStream();
int len = -1;
byte buf[] = new byte[1024];
int offset = startIndex;
while((len=inputStream.read(buf))!=-1) {
owner.writeLocalFile(offset,buf,0,len);
readBytes+=len;
offset+=len;
owner.commitOffset(index,offset);
owner.reportProgress(index,readBytes);
}
owner.taskFinished();
}
} catch (IOException e) {
e.printStackTrace();
try {
owner.reStartTask(index);
} catch (IOException e1) {
e1.printStackTrace();
}
} finally {
if(inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(connection != null) {
connection.disconnect();
}
try {
owner.closeTaskResource(index);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package org.blackfoxer.cat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class JavaXunlei {
private static final BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
private static String storeDir = null;
public static void main(String args[]) throws Exception {
storeDir = getInput("请先设置你的文件存储目录:");
int taskNum = getIntInput("请输入你的开启下载的线程数:");
while(true) {
String url = getInput("请输入文件链接地址:");
Job job = new Job(url,storeDir,taskNum);
job.startJob();
}
}
private static int getIntInput(String message) throws IOException {
String number = getInput(message);
while(!number.matches("\\d+")) {
System.out.println("线程数必须是1个整数");
number = getInput(message);
}
return Integer.parseInt(number);
}
private static String getInput(String message) throws IOException {
System.out.print(message);
String line = in.readLine();
while(line == null || line.trim().length()<1) {
System.out.print(message);
line = in.readLine();
}
return line.trim();
}
}
java版迅雷下载源码分享相关推荐
- java版坦克大战源码分享
前言 利用摸鱼时间学了个java的游戏引擎FXGL 本游戏是基于jdk17和FXGL开发的 操作 按键 功能 WSAD 移动 空格 发射子弹 E 使用技能 道具说明 道具 玩家吃到 敌人吃到 星星 子 ...
- 插件一:JAVA微信砍价活动源码分享[商品帮砍到0元,免费领取奖品]
插件一:微信砍价活动源码分享 [商品帮砍到0元,免费领取奖品] 活动描述: 砍价活动即公众号向粉丝推广的0价赠商品(或优惠价购商品)活动,用户通过分享好友帮其砍价,可将价格从原价一路砍到底价,并抢得名 ...
- java版苹果免签源码超级签名免签源码
介绍: 苹果超级签名是什么我就不在赘述了,项目由java+vue开发 功能: 1.支持登录注册,拥有共有池,可上传证书等基本操作,支持用户自行上传证书 2.支持修改分发页面轮播图,简介,安卓合并 3. ...
- Java版赤色要塞源码分析
1.框架与环境搭建 1.1 本游戏使用了以下框架 [url=http://slick.ninjacave.com/]slick2d[/url] [url=http://www.lwjgl.org/]l ...
- java版聚合支付源码Spring Cloud+Spring Boot+mybatis+security+uniapp+Redis+MQ+VR全景+b2b2c多商家入驻前后端分离商城源码
@源码地址来源: https://minglisoft.cn/honghu/business.html 电商微信支付.支付宝支付.余额支付代码 package com.honghu.cloud.con ...
- Modbus通信协议+Modbus串口调试工具+Java版协议解析源码
网络与串口二合一调试助手TCPCOM: https://download.csdn.net/download/liuyuan_java/87454762 Modbus调试工具,模拟串口调试工具 htt ...
- Java版知识付费源码 Spring Cloud+Spring Boot+Mybatis+uniapp+前后端分离实现知识付费平台
提供职业教育.企业培训.知识付费系统搭建服务.系统功能包含:录播课.直播课.题库.营销.公司组织架构.员工入职培训等. 提供私有化部署,免费售后,专业技术指导,支持PC.APP.H5.小程序多终端同步 ...
- JAVA版B2B2C商城源码 拼团商城 分销商城 springboot商城多商家入驻商城系统
@源码地址来源: https://minglisoft.cn/honghu2/business.html 直播带货源码: /*** Copyright © 2012-2017 <a href=& ...
- JAVA版B2B2C商城源码多商户入驻商城
三勾商城多商户是开发友好的微信小程序商城,框架支持SAAS,支持发布 iOS + Android + 公众号 + H5 + 各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)等多个平台,不可多得 ...
最新文章
- [JAVAEE] Thymeleaf 基本语法:常用表达式
- python 列表筛选数据
- 【Android】Mac Android adb 配置
- bom event周期_DOM-BOM-EVENT(1)
- 《朝花夕拾》金句摘抄(二)
- 个人计算机有控制器和运算器吗,cpu是由控制器和运算器组成的对还是错
- python中的event_Python event
- 启动另一个activity
- android开发常用技术,[转载]Android开发常用调试技术记录
- java中bash应用_在bash脚本中查找java应用程序的进程ID(以查看目标应用程序是否已在运行)...
- 【ZOJ 4070】Function and Function
- 编译aspell时出错
- 晶振封装与频率对照表
- c语言判断二次函数,知识:六法搞定二次函数解析式的确定
- 嵌入式系统开发笔记17:CJ/T-188 冷热量表协议解析6
- 计算机辅助翻译专业实训报告,计算机辅助翻译实训报告格式(7页)-原创力文档...
- 网站微调是什么意思?能带来什么作用?
- ubuntu NFS SCP SFTP
- 浅析语音对讲功能在车载监控系统中的应用意义
- 基于DDD的现代ASP.NET开发框架--ABP系列文章总目录