基本逻辑:
使用scheduledExecutorService.scheduleAtFixedRate()方法启动一线程每延迟1秒统计1次下载信息
文件切分多块,每块用一个线程下载,全部下载完成后合并文件切分的多个临时文件,合并完成后删除临时文件
代码:
业务主类:
/**
* @author YongXin
* @date Created in 2022/10/2 10:37
*/
public class Downloader {
private static final Logger log = LoggerFactory.getLogger(Downloader.class);
public ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
//创建线程池对象
public ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(DownloaderConstant.THREAD_NUM, DownloaderConstant.THREAD_NUM, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
private CountDownLatch countDownLatch = new CountDownLatch(DownloaderConstant.THREAD_NUM);
public void download(String downloadUrl){
HttpConnection connection = HttpConnection.create(downloadUrl, Proxy.NO_PROXY);
HttpURLConnection httpURLConnection = connection.getHttpURLConnection();
String fileName = FileUtil.getName(downloadUrl);
fileName = "D:\\" + fileName;
//本地文件大小
long localFileLength = FileUtil.size(new File(fileName));
//下载文件总大小
int contentLength = httpURLConnection.getContentLength();
//判断文件是否已下载完毕
if (localFileLength >= contentLength) {
log.info("文件已下载完毕" + fileName);
return;
}
//创建获取任务信息的下载对象
DownloaderInfoThread downloaderInfoThread = new DownloaderInfoThread(contentLength);
//将任务交给线程执行,每隔一秒执行一次
scheduledExecutorService.scheduleAtFixedRate(downloaderInfoThread, 1, 1, TimeUnit.SECONDS);
//切分任务下载
ArrayList<Future> futures = new ArrayList<>();
split(downloadUrl, futures, httpURLConnection);
try {
countDownLatch.await();//所有线程执行完毕,计数器置0后才继续执行下边代码
} catch (InterruptedException e) {
e.printStackTrace();
}
//合并文件
boolean merge = merge(fileName);
if (merge) {
//清楚临时文件
clearTemp(fileName);
}
System.out.println("下载完成");
connection.disconnect();
scheduledExecutorService.shutdownNow();
//关闭线程池
threadPoolExecutor.shutdown();
}
/**
* 文件切分
*
* @param url
* @param futureList
*/
public void split(String url, ArrayList<Future> futureList, HttpURLConnection httpURLConnection) {
//获取下载文件的大小
long contentLength = httpURLConnection.getContentLength();
//计算切分后的文件大小
long size = contentLength / DownloaderConstant.THREAD_NUM;
//计算分块个数
for (int i = 0; i < DownloaderConstant.THREAD_NUM; i++) {
//计算下载起始位置
long startPos = i * size;
//计算下载结束位置
long endPos;
if (i == DownloaderConstant.THREAD_NUM - 1) {
//最后一块,不指定结束位置,下载剩余全部
endPos = 0;
} else {
endPos = startPos + size;
}
//如果不是第一块,起始位置要加一
if (startPos != 0) {
startPos += 1;
}
//创建任务
DownloaderTask downloaderTask = new DownloaderTask(url, startPos, endPos, i,countDownLatch);
//将任务提交到线程池当中
Future<Boolean> future = threadPoolExecutor.submit(downloaderTask);
futureList.add(future);
}
}
/**
* 文件合并
* @param fileName
* @return
*/
public boolean merge(String fileName) {
log.info("开始合并");
byte[] buffer = new byte[1024 * 1000];
int len = -1;
try (RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "rw")) {
for (int i = 0; i < DownloaderConstant.THREAD_NUM; i++) {
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(fileName + ".temp" + i))) {
while ((len = bufferedInputStream.read(buffer)) != -1) {
randomAccessFile.read(buffer, 0, len);
}
}
}
log.info("文件合并完毕");
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 清空临时文件
* @param fileName
* @return
*/
public boolean clearTemp(String fileName){
for (int i = 0; i < DownloaderConstant.THREAD_NUM; i++) {
File file = new File(fileName + ".temp" + i);
file.delete();
}
return true;
}
}
下载信息线程类:
/**
* @author YongXin
* @date Created in 2022/10/2 11:44
*/
public class DownloaderInfoThread implements Runnable{
//下载文件总大小
private long httpFileContentLength;
//本地已下载文件大小
public static LongAdder finishedSize = new LongAdder();
//本次累计下载大小
public static volatile LongAdder downSize = new LongAdder();
//前一次累计下载的大小
public double prevSize;
//downSize - prevSize = 这一秒下载大小
public DownloaderInfoThread(long httpFileContentLength) {
this.httpFileContentLength = httpFileContentLength;
}
@Override
public void run() {
//计算文件总大小 单位:MB
String httpFileSize = String.format("%.2f", httpFileContentLength / DownloaderConstant.MB);
//计算每秒下载速度
int speed = (int)((downSize.doubleValue() - prevSize) / 1024d);
prevSize = downSize.doubleValue();
//计算剩余文件大小
double remainSize = httpFileContentLength - finishedSize.doubleValue() -downSize.doubleValue();
//计算剩余时间
String remainTime = String.format("%.1f", remainSize / 1024d / speed);
if ("Infinitys".equalsIgnoreCase(remainTime)){
remainTime = "-";
}
//已下载大小
String currentFileSize = String.format("%.2f", (downSize.doubleValue() - finishedSize.doubleValue()) / DownloaderConstant.MB);
String downInfo = String.format("已下载 %smb/%smb,速度 %skb/s,剩余时间 %ss", currentFileSize, httpFileSize, speed, remainTime);
System.out.println(downInfo);
}
}
切分下载线程类:
/**
* @author YongXin
* @date Created in 2022/10/3 10:19
*/
public class DownloaderTask implements Callable<Boolean> {
public static final Logger log = LoggerFactory.getLogger(DownloaderTask.class);
//下载链接
private String url;
//下载起始位置
private long startPos;
//下载结束位置
private long endPos;
//标识当前是哪一部分的
private int part;
private CountDownLatch countDownLatch;
public DownloaderTask(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;
}
@Override
public Boolean call() throws Exception {
//下载文件名
String fileName = FileUtil.getName(url);
//分块文件名
fileName = fileName + ".temp" + part;
//下载路径
String fileNamePath = DownloaderConstant.PATH + fileName;
//获取分块下载的链接
HttpConnection connection = HttpConnection.create(url, Proxy.NO_PROXY);
HttpURLConnection httpURLConnection = connection.getHttpURLConnection();
log.info("下载的区间是:"+startPos+"-"+endPos);
if (endPos != 0) {
httpURLConnection.setRequestProperty("RANGE", "bytes=" + startPos + "-" + endPos);
} else {
httpURLConnection.setRequestProperty("RANGE", "bytes=" + startPos + "-");
}
try (
InputStream inputStream = httpURLConnection.getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
RandomAccessFile accessFile = new RandomAccessFile(fileNamePath, "rw");
) {
byte[] buffer = new byte[1024 * 1000];
int len = -1;
while ((len = bufferedInputStream.read(buffer)) != -1){
//1秒内下载数据之和,通过原子类进行操作
DownloaderInfoThread.downSize.add(len);
accessFile.read(buffer,0,len);
}
} catch (FileNotFoundException e) {
log.error("文件找不到");
return false;
} catch (Exception e) {
log.error("文件下载失败");
return false;
}finally {
httpURLConnection.disconnect();
countDownLatch.countDown();
}
return true;
}
}
枚举类:
/**
* @author YongXin
* @date Created in 2022/10/3 10:25
*/
public class DownloaderConstant {
//下载地址
public static final String PATH = "D:\\";
//兆
public static final Double MB = 1024d*1024d;
//线程数量
public static final int THREAD_NUM = 5;
}