所用和需要学习的知识点
- http
- ScheduledExecutorService生成打印线程,按时间隔输出信息
- 利用ThreadPoolExecutor线程池进行多线程分片下载
- 使用原子类保证数据在线程中安全性
利用scanner获取控制台输入
Scanner sc =new Scanner(System.in);
String url=null;
while (true){
System.out.println("请输入下载地址:");
Scanner sc =new Scanner(System.in);
url = sc.next();
break;
}
我们在控制台获取输入的下载地址。
HttpURLConnection
编写工具类,获取下载HttpURLConnection
和获取下载文件名称
// 获取http链接
public static HttpURLConnection getHttpURLConnection(String url) throws IOException {
URL httpUrl = new URL(url);
HttpURLConnection httpUrlConnection = (HttpURLConnection)httpUrl.openConnection();
// 向文件所在服务器 伪造请求信息
httpUrlConnection.addRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1");
return httpUrlConnection;
}
// 获取文件名称
public static String getFileName(String url){
int index = url.lastIndexOf("/");
String fileName = url.substring(index);
return fileName;
}
获取文件IO流进行下载
其中代码中利用到try(){} catch(){},实现了对输入流输出流的自动关闭
flush()可以强制将缓冲区的内容全部写入输出流
其中 bos.flush() 其实可以忽略这个刷新缓冲流。只调用 bis.close() 和 bos.close() 就可以。
进一步使用try(){} catch{} 连关闭流可以省略
IO流
- 利用上述的http链接获取输入流
- 获取保存路径
- 获取输出流,保存文件
实现单线程下载文件
public static void downLoad(String url){
String fileName = HttpUtils.getFileName(url);
String savePath = Constant.PATH + fileName;
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = HttpUtils.getHttpURLConnection(url);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 利用文件IO流进行下载
try (
// 执行完自动关闭IO流
InputStream inputStream = httpURLConnection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(inputStream);
FileOutputStream fos = new FileOutputStream(savePath);
BufferedOutputStream bos = new BufferedOutputStream(fos);
){
int len=-1;
while ((len = bis.read())!=-1){
bos.write(len);
}
} catch (FileNotFoundException e) {
System.out.println("下载的文件不存在");
} catch (Exception e){
System.out.println("下载失败");
}
finally {
if(httpURLConnection!=null){
httpURLConnection.disconnect();
}
}
}
可以使用QQ下载链接进行尝试
https://dldir1.qq.com/qqfile/qq/PCQQ9.7.9/QQ9.7.9.29065.exe
Logger工具类
编写logger工具类
使用了Java 可变参数,同时构建了打印主函数print
其次构建 info和error函数实现了对print函数的复用,并涉及可变参数
public class LogUtils {
public static void info(String msg, Object... args){
print(msg,"-info-",args);
}
public static void error(String msg, Object... args){
print(msg,"-error-",args);
}
public static void print(String msg, String level, Object... args){
// 如果包含额外信息
if (args!=null && args.length>0){
msg = msg.replace("{}",(String)args[0]);
}
System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss"))+level+msg);
}
}
获取打印信息
文件下载的时候最好能够展示出下载的速度,已下载文件大小等信息。这里可以每隔一段时间来获取文件的下载信息,比如间隔1秒获取一次,然后将信息打印到控制台。文件下载是一个独立的线程,另外还需要再开启一个线程来间隔获取文件的信息。java.util.concurrent. ScheduledExecutorService,这个类可以帮助我们来实现此功能。
ScheduledExecutorService
在该类中提供了一些方法可以帮助开发者实现间隔执行的效果,下面列出一些常见的方法及其参数说明。我们可以通过下面方式来获取该类的对象,其中1标识核心线程的数量
ScheduledExecutorService s = Executors.newScheduledThreadPool(1);
schedule方法
该方法是重载的,这两个重载的方法都是有3个形参,只是第一个形参不同。
- Runnable / Callable 可以传入这两个类型的任务
- long delay 时间数量
- TimeUnit unit 时间单位
该方法的作用是让任务按照指定的时间延时执行
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
ses.schedule(new Runnable() {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
},2, TimeUnit.SECONDS);
ses.shutdown();
}
scheduleAtFixedRate方法
该方法的作用是按照指定的时间延时执行,并且每隔一段时间再继续执行
- Runnable command 执行的任务
- long initialDelay 延时的时间数量
- long period 间隔的时间数量
l TimeUnit unit 时间单位
倘若在执行任务的时候,耗时超过了间隔时间,则任务执行结束之后直接再次执行,而不是再等待间隔时间执行。 意思就是执行任务的时间 与 间隔的时间相比,这里间隔的时间包含执行任务的时间
scheduleWithFixedDelay方法
该方法的作用是按照指定的时间延时执行,并且每隔一段时间再继续执行
- Runnable command 执行的任务
- long initialDelay 延时的时间数量
- long period 间隔的时间数量
- TimeUnit unit 时间单位
在执行任务的时候,无论耗时多久,任务执行结束之后都会等待间隔时间之后再继续下次任务。
构建下载信息类
首先构建下载信息类实现Runnable接口,然后我们利用scheduleWithFixedDelay新建该进程方法,每间隔一秒执行run()方法更新并显示下载信息。
主要是存储文件下载信息,同时由于我们使用字节流进行下载,我们如果使用常见的MB显示,记得进行单位转换Constant.MB=1024d * 1024d
package com.yqyang.down;
import com.yqyang.constant.Constant;
public class DownLoadInfo implements Runnable{
// 文件总大小
double fileSize;
// 剩余文件大小
double lastFileSize;
// 当前下载的总大小 多个线程 使用volatile强制从主内存读取
volatile double downSize;
// 前1s下载的总大小
double predownSize;
public DownLoadInfo(double fileSize) {
this.fileSize = fileSize;
lastFileSize = fileSize;
}
@Override
public void run() {
// 下载使用的是字节流 单位换算 同时为了方便打印转化为字符串
String fileSizeMB = String.format("%.2f",fileSize / Constant.MB);
// 1s 内下载文件大小
double secondDownSize= downSize -predownSize;
predownSize = downSize;
// 1s 下载速度 kb/s 同时为了方便打印转化为字符串
String downSpeed = String.format("%.2f",secondDownSize / 1024d);;
// 剩余下载大小 同时为了方便打印转化为字符串
lastFileSize = (fileSize - downSize);
String lastFileSizeMB = String.format("%.2f",lastFileSize/Constant.MB);
// 剩余时间
String lastTime = String.format("%.0f",lastFileSize/secondDownSize);
String downInfo = String.format("剩余文件大小%sMB 文件大小 %sMB 下载速度 %sKB/s 剩余时间%ss",lastFileSizeMB,fileSizeMB,downSpeed,lastTime);
// 打印下载信息
System.out.print("\r");
System.out.print(downInfo);
}
}
- 实现Runnable接口
- 对于会在多个线程使用的变量,使用volatile声明,使其强制从内存读取
- 使用
System.out.print("\r");
不会换行,刷新当前显示字符串
更新DownLoad
我们需要更新DownLoad代码
首先我们可以先获取,要保存的地方是否存在文件,并且与要下载的文件大小是否相同,
编写FileUtils
package com.yqyang.utils;
import java.io.File;
public class FileUtils {
public static double getLocalFileSize(String path){
File file = new File(path);
// 首先看文件是否存在 其次看是不是文件, 是的话返回文件长度
return file.exists() && file.isFile() ? file.length() : 0;
}
}
添加线程,打印下载信息
public static void downLoad(String url){
String fileName = HttpUtils.getFileName(url);
String savePath = Constant.PATH + '/' +fileName;
HttpURLConnection httpURLConnection = null;
DownLoadInfo downLoadInfo;
// 获取本地文件大小
double localFileSize = FileUtils.getLocalFileSize(savePath);
try {
httpURLConnection = HttpUtils.getHttpURLConnection(url);
//
long contentLength = httpURLConnection.getContentLengthLong();
String temp= String.valueOf(contentLength);
// 本地是否已经曾经下载完成
if (localFileSize*Math.pow(10, temp.length())>=contentLength) {
LogUtils.info("文件已下载");
return;
}
// 构建下载信息类
downLoadInfo=new DownLoadInfo(contentLength);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 新建打印信息线程 记得关闭
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
// 设置任务和执行时间
scheduledExecutorService.scheduleWithFixedDelay(downLoadInfo, 1, 1, TimeUnit.SECONDS);
try (
// 执行完自动关闭IO流
InputStream inputStream = httpURLConnection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(inputStream);
FileOutputStream fos = new FileOutputStream(savePath);
BufferedOutputStream bos = new BufferedOutputStream(fos);
){
byte[] buff = new byte[1024*100];
int len=-1;
while ((len = bis.read(buff))!=-1){
bos.write(buff,0,len);
// ==== 添加关键下载信息 ====
downLoadInfo.downSize+=len;
}
} catch (FileNotFoundException e) {
LogUtils.error("未找到{}文件", fileName);
} catch (Exception e){
LogUtils.error("下载失败");
}
finally {
if(httpURLConnection!=null){
httpURLConnection.disconnect();
}
// 关闭
scheduledExecutorService.shutdownNow();
}
}/
线程池-ThreadPoolExecutor
线程在创建,销毁的过程中会消耗一些资源,为了节省这些开销,jdk添加了线程池。线程池节省了开销,提高了线程使用的效率。阿里巴巴开发文档中建议在编写多线程程序的时候使用线程池。
ThreadPoolExecutor 构造方法
- corePoolSize
- 线程池中核心线程的数量
- maximumPoolSize
- 线程池中最大线程的数量,是核心线程数量和非核心线程数量之和
- keepAliveTime
- 非核心线程空闲的生存时间
- unit
- keepAliveTime的生存时间单位
- workQueue
- 当没有空闲的线程时,新的任务会加入到workQueue中排队等待
- threadFactory
- 线程工厂,用于创建线程
- handler
- 拒绝策略,当任务太多无法处理时的拒绝策略
线程池工作状态
对于怎么安排非核心线程,和阻塞队列有关
比如使用ArrayBlockingQueue, 核心线程满之后,会先进阻塞队列,阻塞队列满之后会调用非核心线程
线程池的状态
线程池中有5个状态,分别是:
· RUNNING
创建线程池之后的状态是RUNNING
SHUTDOWN
该状态下,线程池就不会接收新任务,但会处理阻塞队列剩余任务,相对温和。
STOP
该状态下会中断正在执行的任务,并抛弃阻塞队列任务,相对暴力。
· TIDYING
任务全部执行完毕,活动线程为 0 即将进入终止
· TERMINATED
线程池终止
线程池的关闭
线程池使用完毕之后需要进行关闭,提供了以下两种方法进行关闭
- shutdown()
该方法执行后,线程池状态变为 SHUTDOWN,不会接收新任务,但是会执行完已提交的任务,此方法不会阻塞调用线程的执行。
- shutdownNow()
该方法执行后,线程池状态变为 STOP,不会接收新任务,会将队列中的任务返回,并用 interrupt 的方式中断正在执行的任务。
工作队列
jdk中提供的一些工作队列workQueue
- SynchronousQueue
直接提交队列
·* ArrayBlockingQueue
有界队列,可以指定容量
· LinkedBlockingDeque
无界队列
· PriorityBlockingQueue
优先任务队列,可以根据任务优先级顺序执行任务
public static void main(String[] args) {
// 新建线程池
ThreadPoolExecutor executor=new ThreadPoolExecutor(3, 4,10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));
// 设置任务
Runnable r=()->{
System.out.println(Thread.currentThread().getName());
};
int i=5;
// 打印线程池信息
System.out.println(executor);
for (int i1 = 0; i1 < i; i1++) {
// 线程执行任务
executor.execute(r);
}
System.out.println(executor);
}
使用线程池实现多线程分片下载
完善HttpUtils方法-分片下载
重写getHttpURLConnection
方法, 实现分片下载
下载url 中startPos - endPos 字节的数据,如果endPos不写表示从startPos下载到最后。
// https://dldir1.qq.com/qqfile/qq/PCQQ9.7.9/QQ9.7.9.29065.exe
// 获取http链接
public static HttpURLConnection getHttpURLConnection(String url) throws IOException {
URL httpUrl = new URL(url);
HttpURLConnection httpUrlConnection = (HttpURLConnection)httpUrl.openConnection();
// 向文件所在服务器 伪造请求信息
httpUrlConnection.addRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1");
return httpUrlConnection;
}
/* 获取分片下载链接 重写方法
*
* */
public static HttpURLConnection getHttpURLConnection(String url,long startPos,long endPos) throws IOException {
// 获取链接
HttpURLConnection httpURLConnection = getHttpURLConnection(url);
if (endPos==0)
// 最后一个分片
httpURLConnection.setRequestProperty("RANGE","bytes="+startPos+"-");
else
httpURLConnection.setRequestProperty("RANGE","bytes="+startPos+"-"+endPos);
return httpURLConnection;
}
完善分片下载任务
因为我们要实现分片下载,所以我们需要用到多线程下载,要构建线程任务。
这里我们实现Callable接口,因为Callable接口可以有返回值,但是Runnable接口没有返回值
countDownLatch
,是一个线程安全的累减器,保证每个线程下载完分片后在继续执行。
public class DownLoadTask implements Callable<Boolean> {
public DownLoadTask(String url, long startPos, long endPos, int part, CountDownLatch countDownLatch) {
this.url = url;
this.startPos = startPos;
this.endPos = endPos;
this.part = part;
this.countDownLatch=countDownLatch;
}
private CountDownLatch countDownLatch;
private String url;
// 下载起始位置
private long startPos;
// 下载结束位置
private long endPos;
// 分片索引
private int part;
@Override
public Boolean call() throws IOException {
// 获取文件名字
String fileName = HttpUtils.getFileName(url);
// 分块的文件名
String saveName = Constant.PATH +"/"+fileName+".temp"+part;
// 获取分块下载的链接
HttpURLConnection httpURLConnection=HttpUtils.getHttpURLConnection(url, startPos, endPos);
System.out.println(Thread.currentThread()+String.valueOf(startPos)+"-"+ String.valueOf(endPos));
try( // 实现自动关闭
InputStream is = httpURLConnection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
RandomAccessFile rw = new RandomAccessFile(saveName, "rw");
){
int len=-1;
byte[] buff = new byte[1024 * 100];
while ((len=bis.read(buff))!=-1){
rw.write(buff,0, len);
DownLoadInfo.downSize.add(len);
}
}catch (FileNotFoundException e){
LogUtils.error("下载文件不存在");
return false;
}catch (Exception e){
LogUtils.error("出错了");
return false;
}finally {
countDownLatch.countDown();
// 记得关闭啊
if (httpURLConnection!=null)
httpURLConnection.disconnect();
}
return true;
}
实现分片下载
其次创建线程池对象
public ThreadPoolExecutor executor=new ThreadPoolExecutor(Constant.THREAD_NUM, Constant.THREAD_NUM,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
很明显我们这里面没有非核心线程。
对文件进行分片,并调用任务
// 文件切分
public void split(String url, long contentLength){
int size = (int) (contentLength / Constant.THREAD_NUM);
for (int i = 0; i < Constant.THREAD_NUM; i++) {
long startPos = i * size;
long endPos;
if (i==Constant.THREAD_NUM-1) endPos=0;
else endPos = startPos + size;
if (i!=0) startPos+=1;
DownLoadTask downLoadTask = new DownLoadTask(url, startPos, endPos, i, countDownLatch);
executor.submit(downLoadTask);
}
}
利用原子类 LongAdder
此时打印信息是有错误的,这里必须保证变量downSize的安全
public class DownLoadInfo implements Runnable{
// 文件总大小
double fileSize;
// 当前下载的总大小 多个线程 使用volatile强制从主内存读取
static volatile LongAdder downSize=new LongAdder();
// 前1s下载的总大小
double predownSize;
public DownLoadInfo(double fileSize) {
this.fileSize = fileSize;
}
@Override
public void run() {
// 下载使用的是字节流 单位换算 同时为了方便打印转化为字符串
String fileSizeMB = String.format("%.2f",fileSize / Constant.MB);
// 1s 内下载文件大小
double secondDownSize= downSize.doubleValue() -predownSize;
predownSize = downSize.doubleValue();
// 1s 下载速度 kb/s 同时为了方便打印转化为字符串
String downSpeed = String.format("%.2f",secondDownSize / 1024d);;
// 剩余下载大小 同时为了方便打印转化为字符串
double lastFileSize = (fileSize - downSize.doubleValue());
String lastFileSizeMB = String.format("%.2f",lastFileSize/Constant.MB);
// 剩余时间
String lastTime = String.format("%.0f",lastFileSize/secondDownSize);
String downInfo = String.format("剩余文件大小%sMB 文件大小 %sMB 下载速度 %sKB/s 剩余时间%ss",lastFileSizeMB,fileSizeMB,downSpeed,lastTime);
System.out.print("\r");
System.out.print(downInfo);
}
}
合并分片文件并删除缓存
public Boolean mergeFile(String fileName){
LogUtils.info("正在合并文件");
try (RandomAccessFile accessFile = new RandomAccessFile(fileName,"rw")) {
for (int i = 0; i < Constant.THREAD_NUM; i++) {
int len=-1;
try ( BufferedInputStream bis=new BufferedInputStream(new FileInputStream(fileName+".temp"+i));){
byte[] buff = new byte[1024 * 100];
while ((len=bis.read(buff))!=-1){
accessFile.write(buff);
}
}
}
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
return false;
}
LogUtils.info("合并文件完成");
return true;
}
public Boolean deleteTempFile(String fileName){
for (int i = 0; i < Constant.THREAD_NUM; i++) {
File file=new File(fileName+".temp"+i);
file.delete();
}
return true;
}
DownLoad
public void downLoad(String url){
String fileName = HttpUtils.getFileName(url);
String savePath = Constant.PATH + '/' +fileName;
HttpURLConnection httpURLConnection = null;
DownLoadInfo downLoadInfo;
// 获取本地文件大小
double localFileSize = FileUtils.getLocalFileSize(savePath);
long contentLength;
try {
httpURLConnection = HttpUtils.getHttpURLConnection(url);
contentLength = httpURLConnection.getContentLengthLong();
String temp= String.valueOf(contentLength);
// 查看文件是否已经下载完成
if (localFileSize*Math.pow(10, temp.length())>=contentLength) {
LogUtils.info("文件已下载");
return;
}
// 构建下载信息类
downLoadInfo=new DownLoadInfo(contentLength);
// 设置信息线程池每隔一秒执行一次
scheduledExecutorService.scheduleWithFixedDelay(downLoadInfo, 1, 1, TimeUnit.SECONDS);
split(url, contentLength);
// 等待所有线程下载完毕
countDownLatch.await();
System.out.print("/r");
System.out.println("所有文件下载完成");
mergeFile(savePath);
deleteTempFile(savePath);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if(scheduledExecutorService!=null) scheduledExecutorService.shutdown();
if(executor!=null) executor.shutdown();
}
}
完整代码
package com.yqyang;
import com.yqyang.down.DownLoad;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
String url=null;
while (true){
System.out.println("请输入下载地址:");
Scanner sc =new Scanner(System.in);
url = sc.next();
break;
}
DownLoad downLoad = new DownLoad();
downLoad.downLoad(url);
}
}
com.yyq.constant Constant.java
package com.yqyang.constant;
import java.util.Stack;
public class Constant {
public static final String PATH="D:\\WorkSpace\\JavaWorkSpace\\downLoader\\down";
public static final double MB = (1024d * 1024d);
public static final int THREAD_NUM = 5;
}
com.yyq.down DownLoad.java
package com.yqyang.down;
import com.yqyang.constant.Constant;
import com.yqyang.utils.FileUtils;
import com.yqyang.utils.HttpUtils;
import com.yqyang.utils.LogUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.concurrent.*;
public class DownLoad {
// 新建打印信息线程池 记得关闭
public CountDownLatch countDownLatch=new CountDownLatch(Constant.THREAD_NUM);
public ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
// 新建线程池
public ThreadPoolExecutor executor=new ThreadPoolExecutor(Constant.THREAD_NUM, Constant.THREAD_NUM,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
public void downLoad(String url){
String fileName = HttpUtils.getFileName(url);
String savePath = Constant.PATH + '/' +fileName;
HttpURLConnection httpURLConnection = null;
DownLoadInfo downLoadInfo;
// 获取本地文件大小
double localFileSize = FileUtils.getLocalFileSize(savePath);
long contentLength;
try {
httpURLConnection = HttpUtils.getHttpURLConnection(url);
contentLength = httpURLConnection.getContentLengthLong();
String temp= String.valueOf(contentLength);
// 查看文件是否已经下载完成
if (localFileSize*Math.pow(10, temp.length())>=contentLength) {
LogUtils.info("文件已下载");
return;
}
// 构建下载信息类
downLoadInfo=new DownLoadInfo(contentLength);
// 设置信息线程池每隔一秒执行一次
scheduledExecutorService.scheduleWithFixedDelay(downLoadInfo, 1, 1, TimeUnit.SECONDS);
split(url, contentLength);
// 等待所有线程下载完毕
countDownLatch.await();
System.out.print("/r");
System.out.println("所有文件下载完成");
mergeFile(savePath);
deleteTempFile(savePath);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if(scheduledExecutorService!=null) scheduledExecutorService.shutdown();
if(executor!=null) executor.shutdown();
}
}
// 文件切分
public void split(String url, long contentLength){
int size = (int) (contentLength / Constant.THREAD_NUM);
for (int i = 0; i < Constant.THREAD_NUM; i++) {
long startPos = i * size;
long endPos;
if (i==Constant.THREAD_NUM-1) endPos=0;
else endPos = startPos + size;
if (i!=0) startPos+=1;
DownLoadTask downLoadTask = new DownLoadTask(url, startPos, endPos, i, countDownLatch);
executor.submit(downLoadTask);
}
}
// 文件合并
public Boolean mergeFile(String fileName){
LogUtils.info("正在合并文件");
try (RandomAccessFile accessFile = new RandomAccessFile(fileName,"rw")) {
int len=-1;
for (int i = 0; i < Constant.THREAD_NUM; i++) {
try ( BufferedInputStream bis=new BufferedInputStream(new FileInputStream(fileName+".temp"+i));){
byte[] buff = new byte[1024 * 100];
while ((len=bis.read(buff))!=-1){
accessFile.write(buff,0, len);
}
}
}
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
return false;
}
LogUtils.info("合并文件完成");
return true;
}
public Boolean deleteTempFile(String fileName){
for (int i = 0; i < Constant.THREAD_NUM; i++) {
File file=new File(fileName+".temp"+i);
file.delete();
}
return true;
}
}
com.yyq.down DownLoadInfo.java
package com.yqyang.down;
import com.yqyang.constant.Constant;
import java.util.concurrent.atomic.LongAdder;
public class DownLoadInfo implements Runnable{
// 文件总大小
double fileSize;
// 当前下载的总大小 多个线程 使用volatile强制从主内存读取
static volatile LongAdder downSize=new LongAdder();
// 前1s下载的总大小
double predownSize;
public DownLoadInfo(double fileSize) {
this.fileSize = fileSize;
}
@Override
public void run() {
// 下载使用的是字节流 单位换算 同时为了方便打印转化为字符串
String fileSizeMB = String.format("%.2f",fileSize / Constant.MB);
// 1s 内下载文件大小
double secondDownSize= downSize.doubleValue() -predownSize;
predownSize = downSize.doubleValue();
// 1s 下载速度 kb/s 同时为了方便打印转化为字符串
String downSpeed = String.format("%.2f",secondDownSize / 1024d);;
// 剩余下载大小 同时为了方便打印转化为字符串
double lastFileSize = (fileSize - downSize.doubleValue());
String lastFileSizeMB = String.format("%.2f",lastFileSize/Constant.MB);
// 剩余时间
String lastTime = String.format("%.0f",lastFileSize/secondDownSize);
String downInfo = String.format("剩余文件大小%sMB 文件大小 %sMB 下载速度 %sKB/s 剩余时间%ss",lastFileSizeMB,fileSizeMB,downSpeed,lastTime);
System.out.print("\r");
System.out.print(downInfo);
}
}
com.yyq.down DownLoadTask.java
package com.yqyang.down;
import com.yqyang.constant.Constant;
import com.yqyang.utils.HttpUtils;
import com.yqyang.utils.LogUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
public class DownLoadTask implements Callable<Boolean> {
public DownLoadTask(String url, long startPos, long endPos, int part, CountDownLatch countDownLatch) {
this.url = url;
this.startPos = startPos;
this.endPos = endPos;
this.part = part;
this.countDownLatch=countDownLatch;
}
private CountDownLatch countDownLatch;
private String url;
// 下载起始位置
private long startPos;
// 下载结束位置
private long endPos;
// 分片索引
private int part;
@Override
public Boolean call() throws IOException {
// 获取文件名字
String fileName = HttpUtils.getFileName(url);
// 分块的文件名
String saveName = Constant.PATH +"/"+fileName+".temp"+part;
// 获取分块下载的链接
HttpURLConnection httpURLConnection=HttpUtils.getHttpURLConnection(url, startPos, endPos);
System.out.println(Thread.currentThread()+String.valueOf(startPos)+"-"+ String.valueOf(endPos));
try( // 实现自动关闭
InputStream is = httpURLConnection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
RandomAccessFile rw = new RandomAccessFile(saveName, "rw");
){
int len=-1;
byte[] buff = new byte[1024 * 100];
while ((len=bis.read(buff))!=-1){
rw.write(buff,0, len);
DownLoadInfo.downSize.add(len);
}
}catch (FileNotFoundException e){
LogUtils.error("下载文件不存在");
return false;
}catch (Exception e){
LogUtils.error("出错了");
return false;
}finally {
countDownLatch.countDown();
// 记得关闭啊
if (httpURLConnection!=null)
httpURLConnection.disconnect();
}
return true;
}
}
工具类
com.yqyang.utils FileUtils .java
package com.yqyang.utils;
import java.io.File;
public class FileUtils {
public static double getLocalFileSize(String path){
File file = new File(path);
return file.exists() && file.isFile() ? file.length() : 0;
}
}
com.yqyang.utils HttpUtils .java
package com.yqyang.utils;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class HttpUtils {
// https://dldir1.qq.com/qqfile/qq/PCQQ9.7.9/QQ9.7.9.29065.exe
// 获取http链接
public static HttpURLConnection getHttpURLConnection(String url) throws IOException {
URL httpUrl = new URL(url);
HttpURLConnection httpUrlConnection = (HttpURLConnection)httpUrl.openConnection();
// 向文件所在服务器 伪造请求信息
httpUrlConnection.addRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1");
return httpUrlConnection;
}
/*
* 获取文件名称
*
* */
public static String getFileName(String url){
int index = url.lastIndexOf("/");
String fileName = url.substring(index+1);
return fileName;
}
/* 获取分片下载链接 重写方法
*
* */
public static HttpURLConnection getHttpURLConnection(String url,long startPos,long endPos) throws IOException {
// 获取链接
HttpURLConnection httpURLConnection = getHttpURLConnection(url);
if (endPos==0)
// 最后一个分片
httpURLConnection.setRequestProperty("RANGE","bytes="+startPos+"-");
else
httpURLConnection.setRequestProperty("RANGE","bytes="+startPos+"-"+endPos);
return httpURLConnection;
}
}
com.yqyang.utils LogUtils .java
package com.yqyang.utils;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class LogUtils {
public static void info(String msg, Object... args){
print(msg,"-info-",args);
}
public static void error(String msg, Object... args){
print(msg,"-error-",args);
}
public static void print(String msg, String level, Object... args){
// 如果包含额外信息
if (args!=null && args.length>0){
msg = msg.replace("{}",(String)args[0]);
}
System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss"))+level+msg);
}
}