zoukankan      html  css  js  c++  java
  • Java多线程的下载器(1)

    实现了一个基于Java多线程的下载器,可提供的功能有:

    1. 对文件使用多线程下载,并显示每时刻的下载速度
    2. 对多个下载进行管理,包括线程调度,内存管理等。

    一:单个文件下载的管理

    1. 单文件下载类层次

    首先简要介绍一下单个文件下载管理的类层次:

    来一张图来表示。


    1. 为需要下载的文件创建一个Download类,Download负责管理该文件下载时的线程管理、文件管理、当前速度计算等操作。
    2. 根据线程的数目tNum,将该文件分为tNum段,每段为一个DownloadBlock。在实际下载的过程中,并不是一次把所有的东西下载完,而是每次下载固定size的一段Di。所以每个DownloadBlock又会分成n段
    3. 为每个DownloadBlock申请一个线程DownloadThread。其主要作用就是每次下载一段Di,并将其写入到文件中。

    2. 单文件下载

    对于单个下载,步骤如下

    1. 连接资源服务器,获取资源信息,创建文件
    2.  切分资源,为每个线程分配固定的下载区域。

    1)封装下载的属性

    在建立下载之前,我们把每一次下载进行抽象封装。

    首先把URL、目标文件等封装在一个DownloadConfig类中。

    其中包含了4个属性:

    private URL url; //文件下载地址
    private File file; //下载文件保存目标文件
    private int nthread; //下载该文件需要的线程数
    private int priority; //该下载的优先级

    2)连接资源服务器,获取资源信息,创建文件,并指定文件大小

    length = config.getUrl().openConnection().getContentLength();
    RandomAccessFile file = new RandomAccessFile(config.getFile(), "rw");    //随机读取
    file.setLength(length);
    file.close();

    3)切分资源,为每个线程分配固定的下载区域,并将当前的下载加入到队列中

    int size = length / config.getNthread();
    for(int i = 0; i < config.getNthread(); i++){
        int start = i * size;
        int len;
        if(i == config.getNthread() - 1)
            len = length - start;
        else len = size;
    //并将当前的下载加入到下载队列中
        addDownloadBlock(getDownloadBlock(start, len));
    }

    3)启动线程进行下载

    下载的步骤如下:

       1. 创建缓存,创建连接。设置获取资源数据的范围,创建文件,并设置写入位置

    //创建缓存
    byte [] b;
    if(block.getLength() < Constants.BYTES_READ)
        b = new byte[(int)block.getLength()];
    else
        b = new byte[Constants.BYTES_READ];
    
    //创建连接。设置获取资源数据的范围,从startPos到endPos
    URLConnection con = null;
    con.setRequestProperty("Range", "bytes=" + block.getStart() + "-" + block.getStart()+block.getLength()-1);
    RandomAccessFile file = new RandomAccessFile(block.getDownload().getConfig().getFile(), "rw");//创建RandomAccessFile
    file.seek(block.getStart()); //从startPos开始写入

      2. 如果当前block的length大于0,则从URL资源处获取固定大小的资源,并将其写入到文件中。

      3 .更新block块的start,以及length,如果length大于0,继续进行2,否则则表示当前block已经下载完毕,退出该线程。

    InputStream in = block.getDownload().getConfig().getUrl().openStream();
    int n;
    
    //对该block内的文件进行下载,
    while(count < block.getLength()){
        if (needSplit()) { // 检查该Block是否还需要分块(即当前block剩余的大小大于一次下载的量)
            long newLength = (block.getLength() - count) / 2;
            long newStart = block.getStart() + block.getLength() - newLength;
            DownloadBlock newBlock = block.getDownload().getDownloadBlock(newStart, newLength);
            block.setLength(block.getLength() - newLength);
            block.getDownload().addDownloadBlock(newBlock);
        }
    
    
        //写入文件
        n = in.read(b);
        if(n < 0){
            break;
        }else if(count + n > block.getLength()){
            file.write(b, 0, (int)(block.getLength() - count));
            count = block.getLength();
        }else {
            count += n;
            file.write(b, 0, n);
        }
        
        // set block count in download
        if(n > 0){
            //统计每个block中已经下载的段的个数,用于计算当前下载的速度。
            block.getDownload().setBlockCount(block.getStart(), count);
        }
    }
    
    in.close();
    file.close();

    二 . 当前文件下载速度与进度计算

    如第一个图所表示的,每个Block中又分为了很多的段D1、D2、…Dn,因此当为 了计算当前下载的速度,需要将下载的段D的数量统计出来,这里使用了一个ConcurrentHashMap<Long, Long>来保存每个block已经下载完成的段D的数目。其中key为每个block的start值,而value为该block已经下载完的段 D。
    在当前时刻,我们需要统计当前Download已经下载完成段D的数量,然后再和上一时刻的相比较,则可以得出当前的下载速度。具体代码见下:

    class CheckSpeedTask extends TimerTask{
        
        private static final Log log = LogFactory.getLog(CheckSpeedTask.class);
        
        private Download download;
        private ConcurrentHashMap<Long, Long> blockCounts; 
        
        private long speed = 0; // Byte/S
        private long count = 0; // Total downloaded byte count
        private long lastCount = 0;
        private long time = 0; // Check time
        private long lastTime = 0;
        
        public CheckSpeedTask(Download download, long startTime, ConcurrentHashMap<Long, Long> blockCounts){
            this.download = download;
            this.lastTime = startTime;
            this.blockCounts = blockCounts;
        }
        
        @Override
        public void run() {
            try {    
                time = System.currentTimeMillis();
                count = 0;
    //需要统计当前已经下载完成段D的数量。
                for(long c : blockCounts.values()){
                    count += c;
                }
                speed = (count -lastCount)/((time - lastTime)/1000);
                log.debug(blockCounts.size() + " threads are downloading " + download + ", cuttent is " + speed + "Byte/S, " + (count * 1.0)/download.getLength()*100 + "% downloaded");
                download.setCount(count);
                download.setSpeed(speed);
                lastTime = time;
                lastCount = count;
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }        
            
        }
    }

    这样我们就可以在Thread类的run()函数中,计算当前下载的速度

    while(activeThreads.size() > 0 || blockQueue.size() > 0){
        Thread.sleep(1000);
        checkSpeed();
    }

    原文:http://blog.csdn.net/zhzhl202/article/details/7521377

  • 相关阅读:
    HDU 1075 What Are You Talking About(字典树)
    HDU 1075 What Are You Talking About (stl之map映射)
    HDU 1247 Hat’s Words(字典树活用)
    字典树HihoCoder
    HDU 1277全文检索(字典树)
    HDU 3294 Girls' research(manachar模板题)
    HDU 3294 Girls' research(manachar模板题)
    HDU 4763 Theme Section(KMP灵活应用)
    Ordering Tasks UVA
    Abbott's Revenge UVA
  • 原文地址:https://www.cnblogs.com/549294286/p/3410085.html
Copyright © 2011-2022 走看看