开发环境及工具
IDEA
JDK8
UTF-8
constant:存放常量类的包
core:存放了下载器核心类的包
util:存放工具类的包
Main:主类
package com.downloader.constant;
/*常量类*/
public class Constant {
public static final String PATH="D:\\Java_dm\\TestDownloaderPath\\";
public static final double MB=1024d*1024d;
public static final int BYTE_SIZE=1024*100;
public static final int THREAD_NUM=5;
}
package com.downloader.core;
import com.downloader.constant.Constant;
import com.downloader.util.FileUtils;
import com.downloader.util.HttpUtils;
import com.downloader.util.LogUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.concurrent.*;
/*下载器*/
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<>(5));
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);
scheduledExecutorService.scheduleAtFixedRate(downloadInfoThread,1,1, TimeUnit.SECONDS);
//切分任务
ArrayList<Future> list = new ArrayList<>();
spilt(url,list);
countDownLatch.await();//等待所有线程结束
/*合并文件*/
if(merge(httpFileName)){
clearTemp(httpFileName);//清楚文件
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.print("\r");
System.out.print("下载完成");
if (httpURLConnection!=null){
httpURLConnection.disconnect();//关闭连接
}
scheduledExecutorService.shutdownNow();//关闭线程
poolExecutor.shutdown();//关闭线程池
}
}
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;
}
DownloaderTask downloaderTask = new DownloaderTask(url, startPos, endPos,i,countDownLatch);
/*将任务提交到线程池中*/
Future<Boolean> future = poolExecutor.submit(downloaderTask);
futureList.add(future);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean merge(String fileName){
System.out.print("\n");
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;
}
}
package com.downloader.core;
import com.downloader.constant.Constant;
import com.downloader.util.HttpUtils;
import com.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;
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 inputStream=httpURLConnection.getInputStream();
BufferedInputStream bis=new BufferedInputStream(inputStream);
RandomAccessFile accessFile= new RandomAccessFile(httpFileName,"rw");
){
byte[] buffer = new byte[Constant.BYTE_SIZE];
int len=-1;
while ((len=bis.read(buffer))!=-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();//减1操作,等待清零
}
return true;
}
}
package com.downloader.core;
import com.downloader.constant.Constant;
import java.util.concurrent.atomic.LongAdder;
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);
}
}
package com.downloader.util;
import java.io.File;
public class FileUtils {
/*获取本地文件的大小*/
public static long getFileContentLength(String path){
File file = new File(path);
return file.exists()&&file.isFile()?file.length():0;
}
}
package com.downloader.util;
import java.io.IOException;
import java.net.*;
/*http相关工具类*/
public class HttpUtils {
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;
}
/*分块下载方法*/
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连接对象*/
public static HttpURLConnection getHttpURLConnection(String url) throws IOException {
URL httpUrl = new URL(url);
HttpURLConnection urlConnection = (HttpURLConnection)httpUrl.openConnection();
//向文件所在的服务器发送标识信息
urlConnection.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (HTML, like Gecko) Chrome/14.0.835.163 Safari/535.1");
return urlConnection;
}
/*获取下载文件的名称*/
public static String getHttpFileName(String url){
int indexOf = url.lastIndexOf("/");
return url.substring(indexOf+1);
}
}
package com.downloader.util;
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);
}
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);
}
}
package com.downloader;
import com.downloader.core.Downloader;
import com.downloader.util.LogUtils;
import java.util.Scanner;
public class Main {
/*主类,程序的入口*/
public static void main(String[] args) {
String url=null;//用来放下载连接的地址。
if(args!=null && args.length!=0){//判断主函数的传入字符数组是否为空
url=args[0];//如果不为空,对url进行赋值arg[0]
}else {
while (true){//主函数传入字符数组为空,进入while循环
// System.out.println("请输入下载文件的地址。");
LogUtils.info("请输入下载文件的地址");
Scanner scanner=new Scanner(System.in);//在控制台中获取输入的信息。
url=scanner.next();//对url进行赋值,为控制台中输入的信息。
if(url!=null){//如果url不为空,结束while循环。
break;
}
}
}
Downloader downloader = new Downloader();
downloader.download(url);
}
}
链接地址:
https://dldir1.qq.com/qqfile/qq/PCQQ9.7.1/QQ9.7.1.28940.exe
这是一个用多线程下载一个文件的程序,主要是对多线程的理解。