/*
*小型、多线程下载工具2.0
*
*--错误线程的重新下载
*--线程提前下载完成而任务还在继续则应开启新的线程(实现线程控制自动化)
*--断网的时候网速为0,应该暂停所有进程等待
*/
import java.io.*;
import java.net.*;
import java.util.zip.GZIPInputStream;
import java.util.*;
import java.lang.Math;
public class DownloadKit
{
private File fileName;
private long fileSize;
private long allDownloaded;
private HashMap collDown;
// private int costTime; //可视化总耗时
private TreeMap threadUseTime;
private URL url;
private boolean isGzip;
private TreeMap fileSchedule; //文件总共分的段数
private TreeSet startSchedule;
private ArrayList speed; //每个线程速度相关参数
private long headway; //此刻总平均速度
private int ThreadNum;
private HashMap activeThread; //当前活动线程和其开始点
private boolean isCreateThread;
private ArrayList threadGroup; //线程组
DownloadKit()
{
threadGroup=new ArrayList();
fileSchedule=new TreeMap();
speed=new ArrayList();
isCreateThread=true; //默认多线程下载
allDownloaded=0;
collDown=new HashMap();
ThreadNum=0;
activeThread=new HashMap();
threadUseTime=new TreeMap();
headway=0;
// costTime=0;
//---ID和创建顺序会不会创建顺序混乱而不匹配---
}
//文件下载具体实施步骤
public void progressManagement(URL url, File fileName)
{
//构建文件信息
System.out.println("开始构建文件信息");
fileFrame();
Thread deamon=new Thread(new Deamon(),"deamon" );
//启动守护进程
deamon.start();
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.printf("当前时速: %s/s ---%.2f%%\n",countSize(headway,0),(float)allDownloaded/fileSize*100);
}
},1000,1000);
try
{
deamon.join();
} catch (InterruptedException e)
{
e.printStackTrace();
}
timer.cancel();
System.out.println("线程总数:"+ThreadNum);
System.out.printf("--Download completed--%s/%s--\n",countSize(allDownloaded,0),
countSize(fileSize,0));
System.out.println("allDownloaded-fileSize:"+(allDownloaded-fileSize));
System.out.println("\n\n\n\n单个线程用时: "+threadUseTime);
System.out.println("线程ID与起点: "+startSchedule);
System.out.println("活跃线程: "+activeThread);
for (Thread c : threadGroup)
{
System.out.println(c.getName()+"__"+c.getState());
}
System.out.println("线程组: "+threadGroup);
}
//根据次数写入返回下载文件的大小
public String countSize(float size,int useCount)
{
if(size>1024&&useCount<4)
{
return countSize(size/1024,++useCount);
}
else
{
switch (useCount)
{
case 0:return String.format("%.3f",size)+" B";
case 1:return String.format("%.3f",size)+" KB";
case 2:return String.format("%.3f",size)+" MB";
case 3:return String.format("%.3f",size)+" GB";
default:return String.format("%.3f",size)+" TB";
}
}
}
//守护进程,决定何时新建和摧毁进程
class Deamon implements Runnable
{
/*
*新建线程控制标准:
*1.下载速度未达到最大本地带宽
*2.下载速度可以再通过创建线程提高
*3.
*/
public void run()
{
long startPoint;
long beginTime = System.nanoTime();
if(ThreadNum==0)
{
//按线程创建的序号绑定ID
//线程ID从0开始
// long fileSize=100;
long minddleNum = 0;
long frontNum = 0;
long nextNum = 0;
ArrayList queueStack=new ArrayList();
startSchedule=new TreeSet(); //文件中所有线程起点的开始序列
startSchedule.add(fileSize);
Queue originQueue=new LinkedList<>(Arrays.asList((long)0,fileSize/2)); //计算的下一个开始序列存放点
while(ThreadNum<4&&originQueue.peek()!=null&&isCreateThread)//--加文件下载完成为条件--
{
startPoint=originQueue.poll();
queueStack.add(startPoint);
if(startSchedule.add(startPoint)) //线程开始运作
{
threadGroup.add(new Thread(new DownloadThread(ThreadNum,startPoint),
String.valueOf(ThreadNum)));
System.out.println("开始线程:"+ThreadNum);
threadGroup.get(ThreadNum).start();
activeThread.put(ThreadNum, startPoint);
ThreadNum++;
}else
{
System.out.println("达到最大线程数"+"Num: "+ThreadNum);
break;
}
//定义下一个线程起点并将其加入队列
Iterator iter= startSchedule.iterator();
while(iter.hasNext()) //队列生成
{
if((minddleNum=iter.next())==startPoint&&minddleNum!=0)
{
if(iter.hasNext())
{
nextNum=iter.next();
originQueue.add((minddleNum+frontNum)/2);
originQueue.add((minddleNum+nextNum)/2);
break;
}
}
frontNum=minddleNum;
}
}
// System.out.println("\n出队记录:"+queueStack);
// System.out.println("队列:"+originQueue);
// System.out.println("起点:"+startSchedule);
for (Thread t:threadGroup)
{
try
{
t.join();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
long endTime = System.nanoTime();
String costTime = countSecond((float)(endTime-beginTime)/1000/1000/1000,0);
for(DownSpeed c:speed)
{
System.out.printf("ID: %d,costTime: %d downloaded: %d\n",c.ID,c.costTime,collDown.get(c.ID));
}
System.out.printf("--总耗时:%s--平均时速:%s/s\n",costTime,//1228688/0.383478);
countSize(allDownloaded/((float)(endTime-beginTime)/1000/1000/1000),0));
System.out.println();
}
}
}
//下载线程
class DownloadThread implements Runnable
{
int ID;
long downloaded;
long startPoint;
private BufferedInputStream urlRead=null;
private RandomAccessFile RAFile=null;
DownloadThread(int ID,long startPoint)
{
this.ID=ID;
this.startPoint=startPoint;
fileSchedule.put(ID,startPoint);
speed.add(new DownSpeed(ID));
downloaded=0;
}
public void run()
{
HttpURLConnection urlConnection=null;
try
{
urlConnection=(HttpURLConnection)url.openConnection();
}catch(IOException e)
{
System.out.println("ID: "+ID);
e.printStackTrace();
isCreateThread=false;
}
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
urlConnection.setRequestProperty("Keep-Alive", "300");
urlConnection.setRequestProperty("Connection", "keep-alive");
if(fileSize>0)
{//设置请求点
urlConnection.setRequestProperty("Range", "bytes=" + startPoint+ "-" + downloadPoint(startPoint));
System.out.printf(ID+" 请求下载长度: %d-%d\n",startPoint,downloadPoint(startPoint));
}else
isCreateThread=false;
//设置请求超时为5s
urlConnection.setConnectTimeout(5*1000);
// urlConnection.setReadTimeout(10*1000);
try
{
urlConnection.connect(); //创建连接
}catch(Exception e)
{
System.out.println("ID: "+ID);
e.printStackTrace();
isCreateThread=false;
}
//--添加代码,根据返回的状态码判断网页--
try
{
System.out.println("线程 "+ID+" 服务器返回状态码:"+urlConnection.getResponseCode());
if(isGzip)
{//解gzip压缩流
System.out.println("网页使用过 Gzip 格式压缩,正在解压...");
GZIPInputStream Gzip=new GZIPInputStream(urlConnection.getInputStream());
urlRead=new BufferedInputStream(Gzip);
}
else
urlRead=new BufferedInputStream(urlConnection.getInputStream());
//--多个线程同时读取同一文件是否被允许或是线程安全的?---
//可以一直等待直到另一个进程将文件关闭
RAFile=new RandomAccessFile(fileName, "rw");
RAFile.seek(startPoint);
byte[] byt=new byte[1024];
int length=0;
Timer timer = new Timer();
timer.schedule(speed.get(ID),1000,1000);
while((length=urlRead.read(byt))!=-1&&(startPoint+downloaded)60&&useTime<2)
{
// System.out.println(timer+", "+useTime);
return countSecond(timer/60,++useTime);
}
else
{
switch (useTime)
{
case 0:return String.format("%.2f",timer)+" second";
case 1:return String.format("%.2f",timer)+" minute";
default:return String.format("%.2f",timer)+" hour";
}
}
// return null;
}
//利用网页地址的尾端来统一命名要下载的文件
//如果过长或者不可抗因素失败会随机命名。
private File Rename(URL url,String docDir)
{
String name=url.getPath();
name=name.substring(name.lastIndexOf("/")+1); //从最后一个“/”到网页的末尾为名字
//根据字符串命名
char[] charName=name.toCharArray();
int j=0;
for (int i=0; i=255|| name.length() <= 0)
{//随机命名
name=2058+String.valueOf(Math.random()).substring(2)
+name.substring(name.lastIndexOf(".")!=-1?name.lastIndexOf("."):name.length());
}
// 输出的文件流
File sf=new File(docDir);
if(!sf.exists())
{
if(!sf.mkdirs())
{
sf=new File("d:\\");
}
}
int i=1;
String fname=name;
while(new File(sf.getPath()+"\\"+name).exists()) //如果当前目录中文件名已存在
{
int index=fname.lastIndexOf(".")!=-1?fname.lastIndexOf("."):fname.length()-1;
name=fname.substring(0,index)+"("+(i++)+")"+fname.substring(index);
}
this.fileName=new File(sf.getPath()+"\\"+name);
return this.fileName;
}
//文件构造负责获取并构建文件信息
private void fileFrame()
{
HttpURLConnection urlConnection=null;
try
{
urlConnection=(HttpURLConnection)url.openConnection();
System.out.println("网站证书:"+url.getProtocol());
}catch(IOException e)
{
e.printStackTrace();
System.exit(0);
}
if(urlConnection!=null)
{
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
//设置请求超时为5s
urlConnection.setConnectTimeout(5*1000);
urlConnection.setReadTimeout(5*1000);
try
{
urlConnection.connect(); //创建连接
}catch(Exception e)
{
e.printStackTrace();
System.out.println("文件构建失败,连接超时");
System.exit(0);
}
//提取服务器响应的信息
String Gzip_encod=urlConnection.getContentEncoding(); //判断网页是否使用Gzip压缩
isGzip=Gzip_encod!=null&&Gzip_encod.compareTo("gzip")==0;
this.fileSize=urlConnection.getContentLength(); //获得文件长度
System.out.println("文件大小:"+this.fileSize); //--文件大小是-1时,可能是网页,待处理--
fileSchedule.put(90,this.fileSize); //结尾算是线程0 --真正的线程是从1开始的
System.out.println("getDate(): "+urlConnection.getDate());
}
urlConnection.disconnect();
}
private long downloadPoint(long startPoint)
{//--偶尔有NoSuchElementException,应该是未更新schValue之前就使用--
//为下载线程提供尾点
Iterator schValue=new TreeSet(startSchedule).iterator(); //ConcurrentModificationException 17/11/10
while(schValue.hasNext())
{
if(schValue.next()==startPoint)
{
return schValue.next();
}
}
return 0;
}
//下载速度记录进程
class DownSpeed extends TimerTask
{
//--headway偶尔会是负的--
long newDownload=0; //已下载
long oldDownload=0;
long speed=0;
int ID;
int costTime=0; //耗费时间 单位:s
// DownSpeed(){super();}
DownSpeed(int ID)
{
super();
this.ID=ID;
//this.nowDownSize=nowDownSize;
}
void setDownSize(long newDownload)
{
this.newDownload=newDownload;
}
public void run()
{
costTime++;
this.costTime++;
headway=headway-speed+newDownload-oldDownload;
speed=newDownload-oldDownload;
oldDownload=newDownload;
}
void destory()
{
headway-=speed;
threadUseTime.put(ID, costTime);
}
}
public static void main(String[] args) throws Exception
{
String docDir="D:\\java\\DownDocument";
DownloadKit kit=new DownloadKit(); //初始化
URL url=null;
//引导界面
System.out.println("请输入网页地址或链接:");
Scanner scan=new Scanner(System.in);
String urlSc=null;
//用户输入链接
while(urlSc==null||urlSc.equals(""))
{
urlSc=scan.nextLine();
try
{
url=new URL(urlSc);
break;
}catch (MalformedURLException e)
{
System.out.println("链接无法识别,请重新输入:");
}
}
kit.url=url;
//下载文件命名
File rename=kit.Rename(url,docDir);
System.out.println("--"+rename.getName()+"--");
//开始下载
kit.progressManagement(url,rename);
}
}