1. 多线程下载器
1.1 简介
多线程下载器小项目的主要目的是对多线程知识做一些运用,加深对多线程知识的理解,该项目应用的知识点包括下面内容:
- RandomAccessFile类的运用
- HttpURLConnection类的运用
- 线程池的使用
- 原子类LongAdder的运用
- CountDownLatch类的运用
- ScheduledExecutorService类的运用
1.2 环境搭建
1.2.1 基本信息
开发工具:IDEA
JDK版本:8
项目编码:utf-8
1.2.2 创建项目
在开发工具中创建一个java s项目即可。无需导入第三方jar依赖。
1.3 代码解析
1.3.1 包名概览
- constant:存放常量类的包
- core:存放了下载器核心类的包
- util:存放工具类的包
- Main:主类
1.4 文件的下载
从互联网下载文件有点类似于我们将本地某个文件复制到另一个目录下,也会利用IO流进行操作。对于从互联网下载,还需要将本地和下载文件所在的服务器建立连接。
2. 文件下载器的基础代码
2.1 HttpURLConnection
从互联网中下载文件的话,需要与文件所在的服务器建立连接,这里可以使用jdk提供的java.net.HttpURLConnection
类来帮助我们完成这个操作。jdk11中有提供java.net.http.HttpClient
类来替代HttpURLConnection,由于现在使用的是jdk8,因此先不用jdk11中的HttpClient。除此之外还有一些其他第三方提供类可以执行类似的操作,这里就不赘述了。
2.2 用户标识
我们通过浏览器访问某个网站的时候,会将当前浏览器的版本,操作系统版本等信息的标识发送到网站所在的服务器中。当用程序代码去访问网站时,需要将这个标识发送过去。下面的标识大家可以拷贝到程序中。
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1
3. 下载信息
3.1 计划任务
文件下载的时候最好能够展示出下载的速度,已下载文件大小等信息。这里可以每隔一段时间来获取文件的下载信息,比如间隔1秒获取一次,然后将信息打印到控制台。文件下载是一个独立的线程,另外还需要再开启一个线程来间隔获取文件的信息。java.util.concurrent.ScheduledExecutorService
,这个类可以帮助我们来实现此功能。
3.2 ScheduledExecutorService
在该类中提供了一些方法可以帮助开发者实现间隔执行的效果,下面列出一些常见的方法及其参数说明。我们可以通过下面方式来获取该类的对象,其中1标识核心线程的数量。
ScheduledExecutorService s = Executors.newScheduledThreadPool(1);
3.2.1 schedule方法
该方法是重载的,这两个重载的方法都是有3个形参,只是第一个形参不同。
Runnable / Callable<V>
: 可以传入这两个类型的任务long delay
:时间数量TimeUnit unit
: 时间单位
该方法的作用是让任务按照指定的时间延时执行
3.2.2 scheduleAtFixedRate方法
该方法的作用是按照指定的时间延时执行,并且每隔一段时间再继续执行
Runnable command
:执行的任务long initialDelay
: 延时的时间数量long period
:间隔的时间数量TimeUnit unit
:时间单位
倘若在执行任务的时候,耗时超过了间隔时间,则任务执行结束之后直接再次执行,而不是再等待间隔时间执行。
3.2.3 scheduleWithFixedDelay方法
该方法的作用是按照指定的时间延时执行,并且每隔一段时间再继续执行
Runnable command
:执行的任务long initialDelay
:延时的时间数量long period
:间隔的时间数量TimeUnit unit
:时间单位
在执行任务的时候,无论耗时多久,任务执行结束之后都会等待间隔时间之后再继续下次任务。
3.2.4 示例代码
package cn.azzhu.downloader.learn;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Description: TODO ScheduledExecutorService基本使用
* @author: azzhu
* @date:2021/8/23 9:04
*/
public class ScheduleTest {
public static void main(String[] args) {
//获取对象
ScheduledExecutorService s = Executors.newScheduledThreadPool(1);
//延时2秒后开始执行任务,每间隔3秒再执行任务
s.scheduleAtFixedRate(() -> {
System.out.println(System.currentTimeMillis());
//模拟耗时操作
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 2, 3, TimeUnit.SECONDS);
}
public static void schedule() {
//获取对象
ScheduledExecutorService s = Executors.newScheduledThreadPool(1);
//延时2秒之后再执行任务
s.schedule(() -> System.out.println(Thread.currentThread().getName()), 2, TimeUnit.SECONDS);
//关闭
s.shutdown();
}
}
4. 线程池简介
线程在创建,销毁的过程中会消耗一些资源,为了节省这些开销,jdk添加了线程池。线程池节省了开销,提高了线程使用的效率。阿里巴巴开发文档中建议在编写多线程程序的时候使用线程池。
4.1 ThreadPoolExecutor构造方法参数
在juc包下提供了ThreadPoolExecutor类,可以通过该类来创建线程池,这个类中有4个重载的构造方法,最核心的构造方法是有7个形参的,这些参数所代表的意义如下:
-
corePoolSize
:线程池中核心线程的数量 -
maximumPoolSize
:线程池中最大线程的数量,是核心线程数量和非核心线程数量之和 -
keepAliveTime
:非核心线程空闲的生存时间
非核心线程空闲的生存时间
-
unit
:keepAliveTime的生存时间单位 -
workQueue
:当没有空闲的线程时,新的任务会加入到workQueue中排队等待 -
threadFactory
:线程工厂,用于创建线程 -
handler
:拒绝策略,当任务太多无法处理时的拒绝策略
4.2 线程池工作过程
4.3 线程池的状态
线程池中有5个状态,分别是:
RUNNING
:创建线程池之后的状态是RUNNINGSHUTDOWN
:该状态下,线程池就不会接收新任务,但会处理阻塞队列剩余任务,相对温和。STOP
:该状态下会中断正在执行的任务,并抛弃阻塞队列任务,相对暴力。TIDYING
:任务全部执行完毕,活动线程为 0 即将进入终止TERMINATED
:线程池终止
4.4 线程池的关闭
线程池使用完毕之后需要进行关闭,提供了以下两种方法进行关闭
shutdown()
:该方法执行后,线程池状态变为 SHUTDOWN,不会接收新任务,但是会执行完已提交的任务,此方法不会阻塞调用线程的执行。shutdownNow()
:该方法执行后,线程池状态变为 STOP,不会接收新任务,会将队列中的任务返回,并用 interrupt 的方式中断正在执行的任务。
4.5 工作队列
jdk中提供的一些工作队列workQueue
SynchronousQueue
:直接提交队列ArrayBlockingQueue
:有界队列,可以指定容量LinkedBlockingDeque
:无界队列PriorityBlockingQueue
:优先任务队列,可以根据任务优先级顺序执行任务
4.6 示例代码
4.6.1 创建-1
package cn.azzhu.downloader.learn;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description: TODO 线程池对象的创建
* @author: azzhu
* @date:2021/8/23 9:00
*/
public class PoolTest01 {
public static void main(String[] args) {
//创建线程池对象
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 3, 1,
TimeUnit.MINUTES, new ArrayBlockingQueue<>(2));
//创建任务
Runnable r = () -> System.out.println(Thread.currentThread().getName());
//将任务提交给线程池
for (int i = 0; i < 5; i++) {
threadPool.execute(r);
}
}
}
4.6.2 创建-2
package cn.azzhu.downloader.learn;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description: TODO 线程池对象的创建
* @author: azzhu
* @date:2021/8/23 9:01
*/
public class PoolTest02 {
public static void main(String[] args) throws InterruptedException {
//创建线程池对象
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 3, 1,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));
//创建任务
Runnable r = () -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
};
//将任务提交给线程池
for (int i = 0; i < 5; i++) {
threadPool.execute(r);
}
System.out.println(threadPool);
TimeUnit.SECONDS.sleep(10);
System.out.println(threadPool);
}
}
4.6.3 关闭
package cn.azzhu.downloader.learn;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description: TODO 线程池的关闭
* @author: azzhu
* @date:2021/8/23 9:02
*/
public class PoolTest03 {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool = null;
try {
//创建线程池对象
threadPool = new ThreadPoolExecutor(2, 3, 1,
TimeUnit.MINUTES, new ArrayBlockingQueue<>(2));
//创建任务
Runnable r = () -> System.out.println(Thread.currentThread().getName());
//将任务提交给线程池
for (int i = 0; i < 5; i++) {
threadPool.execute(r);
}
} finally {
//关闭线程池
if (threadPool != null) {
// threadPool.shutdown();//温和
// threadPool.shutdownNow();//暴力
threadPool.shutdown();
if (!threadPool.awaitTermination(1,TimeUnit.MINUTES)){
//等待1分钟,如果线程池没有关闭则会执行进来
threadPool.shutdownNow();
}
}
}
}
}
4.6.4 直接提交队列
package cn.azzhu.downloader.learn;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description: TODO 直接提交队列
* @author: azzhu
* @date:2021/8/23 9:02
*/
public class PoolTest04 {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool = null;
try {
//创建线程池对象
threadPool = new ThreadPoolExecutor(2, 3, 1,
TimeUnit.MINUTES, new SynchronousQueue<>());
//创建任务
Runnable r = () -> System.out.println(Thread.currentThread().getName());
//将任务提交给线程池
for (int i = 0; i < 4; i++) {
threadPool.execute(r);
}
} finally {
//关闭线程池
if (threadPool != null) {
// threadPool.shutdown();//温和
// threadPool.shutdownNow();//暴力
threadPool.shutdown();
if (!threadPool.awaitTermination(1,TimeUnit.MINUTES)){
//等待1分钟,如果线程池没有关闭则会执行进来
threadPool.shutdownNow();
}
}
}
}
}
4.6.5 创建方式小结
package cn.azzhu.downloader.learn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
/**
* @Description: TODO jdk提供的便捷的创建线程池的方式
* 阿里巴巴开发文档中不建议使用这些方式创建线程池
* @author: azzhu
* @date:2021/8/23 9:03
*/
public class PoolTest05 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
ExecutorService executorService1 = Executors.newCachedThreadPool();
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
}
}
5.完整代码
5.1 constant包
5.1.1 Constant
package cn.azzhu.downloader.constant;
/**
* @Description: TODO 常量类
* @author: azzhu
* @date:2021/8/23 9:06
*/
public class Constant {
public static final String PATH = "E:\\mytest\\";
public static final double MB = 1024d * 1024d;
public static final int BYTE_SIZE = 1024 * 100;
//线程数量
public static final int THREAD_NUM = 4;
}
5.2 util包
5.2.1 FileUtils
package cn.azzhu.downloader.util;
import java.io.File;
/**
* @Description: TODO 本地文件相关操作
* @author: azzhu
* @date:2021/8/23 8:58
*/
public class FileUtils {
/**
* 获取本地文件的大小
*/
public static long getFileContentLength(String path) {
File file = new File(path);
return file.exists() && file.isFile() ? file.length() : 0;
}
}
5.2.2 HttpUtils
package cn.azzhu.downloader.util;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @Description: TODO http相关工具类
* @author: azzhu
* @date:2021/8/23 8:54
*/
public class HttpUtils {
/**
* 获取下载文件大小
* @param url
* @return
* @throws IOException
*/
public static long getHttpFileContentLength(String url) throws IOException {
int contentLength;
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = getHttpURLConnection(url);
contentLength = httpURLConnection.getContentLength();
} finally {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
return contentLength;
}
/**
* 分块下载
* @param url 下载地址
* @param startPos 下载文件起始位置
* @param endPos 下载文件的结束位置
* @return
*/
public static HttpURLConnection getHttpURLConnection(String url, long startPos, long endPos) throws IOException {
HttpURLConnection httpURLConnection = getHttpURLConnection(url);
LogUtils.info("下载的区间是:{}-{}",startPos,endPos);
if (endPos != 0) {
httpURLConnection.setRequestProperty("RANGE","bytes="+startPos + "-" + endPos);
}else {
httpURLConnection.setRequestProperty("RANGE","bytes="+startPos + "-");
}
return httpURLConnection;
}
/**
* 获取HttpURLConnection链接对象
* @param url 文件的地址
* @return
*/
public static HttpURLConnection getHttpURLConnection(String url) throws IOException {
URL httpUrl = new URL(url);
HttpURLConnection httpURLConnection = (HttpURLConnection)httpUrl.openConnection();
//向文件所在的服务器发送标识信息
httpURLConnection.setRequestProperty("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;
}
/**
* 获取下载文件的名字
* https://down.qq.com/qqweb/PCQQ/PCQQ_EXE/PCQQ2021.exe
* @param url
* @return
*/
public static String getHttpFileName(String url) {
int index = url.lastIndexOf("/");
return url.substring(index + 1);
}
}
5.2.3 LogUtils
package cn.azzhu.downloader.util;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @Description: TODO 日志工具类,模拟日志框架的功能
* @author: azzhu
* @date:2021/8/23 8:52
*/
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);
}
/**
* 定义日志输出格式
* @param msg 日志说明
* @param level 日志级别
* @param args 参数
*/
private static void print(String msg, String level, Object... args) {
if (args != null && args.length > 0) {
msg = String.format(msg.replace("{}", "%s"), args);
}
String name = Thread.currentThread().getName();
System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss")) + " " + name + level + msg);
}
}
5.3 core包
5.3.1 Downloader
package cn.azzhu.downloader.core;
import cn.azzhu.downloader.constant.Constant;
import cn.azzhu.downloader.util.FileUtils;
import cn.azzhu.downloader.util.HttpUtils;
import cn.azzhu.downloader.util.LogUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.concurrent.*;
/**
* @Description: TODO 下载器
* @author: azzhu
* @date:2021/8/23 9:05
*/
public class Downloader {
public ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
//线程池对象
public ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(Constant.THREAD_NUM, Constant.THREAD_NUM, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(Constant.THREAD_NUM));
private CountDownLatch countDownLatch = new CountDownLatch(Constant.THREAD_NUM);
public void download(String url) {
//获取文件名
String httpFileName = HttpUtils.getHttpFileName(url);
//文件下载路径
httpFileName = Constant.PATH + httpFileName;
//获取本地文件的大小
long localFileLength = FileUtils.getFileContentLength(httpFileName);
//获取连接对象
HttpURLConnection httpURLConnection = null;
DownloadInfoThread downloadInfoThread = null;
try {
httpURLConnection = HttpUtils.getHttpURLConnection(url);
//获取下载文件的总大小
int contentLength = httpURLConnection.getContentLength();
//判断文件是否已下载过
if (localFileLength >= contentLength) {
LogUtils.info("{}已下载完毕,无需重新下载",httpFileName);
return;
}
//创建获取下载信息的任务对象
downloadInfoThread = new DownloadInfoThread(contentLength);
//将任务交给线程执行,每隔1秒执行一次
scheduledExecutorService.scheduleAtFixedRate(downloadInfoThread,1,1, TimeUnit.SECONDS);
//切分任务
ArrayList<Future> list = new ArrayList<>();
spilt(url, list);
countDownLatch.await();
/* list.forEach(future -> {
try {
future.get();
} catch (Exception e) {
e.printStackTrace();
}
});*/
//合并文件
if (merge(httpFileName)) {
//清除临时文件
clearTemp(httpFileName);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
System.out.print("\r");
System.out.print("下载完成");
//关闭连接对象
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
//关闭
scheduledExecutorService.shutdownNow();
//关闭线程池
poolExecutor.shutdown();
}
}
/**
* 文件切分
* @param url
* @param futureList
*/
public void spilt(String url, ArrayList<Future> futureList) {
try {
//获取下载文件大小
long contentLength = HttpUtils.getHttpFileContentLength(url);
//计算切分后的文件大小
long size = 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;
}
//如果不是第一块,起始位置要+1
if (startPos != 0) {
startPos++;
}
//创建任务对象
DownloaderTask downloaderTask = new DownloaderTask(url, startPos, endPos,i,countDownLatch);
//将任务提交到线程池中
Future<Boolean> future = poolExecutor.submit(downloaderTask);
futureList.add(future);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 文件合并
* @param fileName
* @return
*/
public boolean merge(String fileName) {
LogUtils.info("开始合并文件{}", fileName);
byte[] buffer = new byte[Constant.BYTE_SIZE];
int len = -1;
try (RandomAccessFile accessFile = new RandomAccessFile(fileName, "rw")) {
for (int i = 0; i < Constant.THREAD_NUM ; i++) {
try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName + ".temp" + i))) {
while ((len = bis.read(buffer)) != -1) {
accessFile.write(buffer,0,len);
}
}
}
LogUtils.info("文件合并完毕{}" + fileName);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/*
清除临时文件
*/
public boolean clearTemp(String fileName) {
for (int i = 0; i < Constant.THREAD_NUM; i++) {
File file = new File(fileName + ".temp" + i);
file.delete();
}
return true;
}
}
5.3.2 DownloaderTask
package cn.azzhu.downloader.core;
import cn.azzhu.downloader.constant.Constant;
import cn.azzhu.downloader.util.HttpUtils;
import cn.azzhu.downloader.util.LogUtils;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
/**
* @Description: TODO 分块下载载任务
* @author: azzhu
* @date:2021/8/23 9:08
*/
public class DownloaderTask implements Callable<Boolean> {
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 httpFileName = HttpUtils.getHttpFileName(url);
//分块的文件名
httpFileName = httpFileName + ".temp" + part;
//下载路径
httpFileName = Constant.PATH + httpFileName;
//获取分块下载的连接
HttpURLConnection httpURLConnection = HttpUtils.getHttpURLConnection(url, startPos, endPos);
try (
InputStream input = httpURLConnection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(input);
RandomAccessFile accessFile = new RandomAccessFile(httpFileName, "rw")
) {
byte[] buffer = new byte[Constant.BYTE_SIZE];
int len = -1;
//循环读取数据
while ((len = bis.read(buffer)) != -1) {
//1秒内下载数据之和, 通过原子类进行操作
DownloadInfoThread.downSize.add(len);
accessFile.write(buffer,0,len);
}
} catch (FileNotFoundException e) {
LogUtils.error("下载文件不存在{}", url);
return false;
} catch (Exception e) {
LogUtils.error("下载出现异常");
return false;
} finally {
httpURLConnection.disconnect();
countDownLatch.countDown();
}
return true;
}
}
5.3.3 DownloadInfoThread
package cn.azzhu.downloader.core;
import cn.azzhu.downloader.constant.Constant;
import java.util.concurrent.atomic.LongAdder;
/**
* @Description: TODO 展示下载信息
* @author: azzhu
* @date:2021/8/23 9:07
*/
public class DownloadInfoThread implements Runnable {
//下载文件总大小
private long httpFileContentLength;
//本地已下载文件的大小,
public static LongAdder finishedSize = new LongAdder();
//本次累计下载的大小
public static volatile LongAdder downSize = new LongAdder();
//前一次下载的大小
public double prevSize;
public DownloadInfoThread(long httpFileContentLength) {
this.httpFileContentLength = httpFileContentLength;
}
@Override
public void run() {
//计算文件总大小 单位:mb
String httpFileSize = String.format("%.2f", httpFileContentLength / Constant.MB);
//计算每秒下载速度 kb
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 ("Infinity".equalsIgnoreCase(remainTime)) {
remainTime = "-";
}
//已下载大小
String currentFileSize = String.format("%.2f", (downSize.doubleValue() - finishedSize.doubleValue()) / Constant.MB);
String downInfo = String.format("已下载 %smb/%smb,速度 %skb/s,剩余时间 %ss",
currentFileSize, httpFileSize, speed, remainTime);
System.out.print("\r");
System.out.print(downInfo);
}
}
5.4 主类
5.4.1 多线程测试
package cn.azzhu.downloader;
import cn.azzhu.downloader.core.Downloader;
import cn.azzhu.downloader.util.LogUtils;
import java.util.Scanner;
/**
* @Description: TODO 主类
* @author: azzhu
* @date:2021/8/23 9:10
*/
public class DownloadMain {
public static void main(String[] args) {
//下载地址
String url = null;
if (args == null || args.length == 0) {
for (; ; ) {
LogUtils.info("请输入下载地址");
Scanner scanner = new Scanner(System.in);
url = scanner.next();
if (url != null) {
break;
}
}
}else {
url = args[0];
}
//https://down.qq.com/qqweb/PCQQ/PCQQ_EXE/PCQQ2021.exe
long startTime = System.currentTimeMillis();
Downloader downloader = new Downloader();
downloader.download(url);
long endTime = System.currentTimeMillis();
System.out.println("总耗时:"+(endTime-startTime));
}
}
5.4.2 单线程测试
package cn.azzhu.downloader;
import cn.azzhu.downloader.constant.Constant;
import cn.azzhu.downloader.util.LogUtils;
import org.apache.http.util.TextUtils;
import java.io.*;
import java.net.*;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Description: TODO 单线程下载
* @author: azzhu
* @date:2021/8/23 9:10
*/
public class DownloadMain2 {
public static void main(String[] args) throws Exception {
//下载地址
String url = null;
if (args == null || args.length == 0) {
for (; ; ) {
LogUtils.info("请输入下载地址");
Scanner scanner = new Scanner(System.in);
url = scanner.next();
if (url != null) {
break;
}
}
}else {
url = args[0];
}
//https://down.qq.com/qqweb/PCQQ/PCQQ_EXE/PCQQ2021.exe
long startTime = System.currentTimeMillis();
//todo 下载文件
downCRL(url,Constant.PATH+"\\qq.exe");
long endTime = System.currentTimeMillis();
System.out.println("总耗时:"+(endTime-startTime));
}
public static void downCRL(String crlurl, String filepath) {
// logger.info("开始下载更新CRL");
// 创建URL 对象
URL url = null;
byte[] b = null;
FileOutputStream fos = null;
InputStream is = null;
HttpURLConnection httpUrlConnection = null;
try {
url = new URL(crlurl);
// 获取 httpUrl连接
httpUrlConnection = (HttpURLConnection) url.openConnection();
is = httpUrlConnection.getInputStream();
b = new byte[1024];
int j;
fos = new FileOutputStream(new File(filepath));
while ((j = is.read(b)) != -1) {
fos.write(b, 0, j);
fos.flush();
}
is.close();
fos.close();
httpUrlConnection.disconnect();
} catch (MalformedURLException e) {
//logger.info("下载CRL列表失败!");
e.printStackTrace();
} catch (IOException e) {
//logger.info("下载CRL列表失败!");
e.printStackTrace();
}
}
}