小型、多线程Java下载工具2.0


/*
 *小型、多线程下载工具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);
    }
}  

你可能感兴趣的:(java,Java,下载器,多线程)