实现了一个基于Java多线程的下载器,可提供的功能有:
1. 对文件使用多线程下载,并显示每时刻的下载速度。
2. 对多个下载进行管理,包括线程调度,内存管理等。
一:单个文件下载的管理
1. 单文件下载类层次
首先简要介绍一下单个文件下载管理的类层次:
来一张图来表示。
- 为需要下载的文件创建一个Download类,Download负责管理该文件下载时的线程管理、文件管理、当前速度计算等操作。
- 根据线程的数目tNum,将该文件分为tNum段,每段为一个DownloadBlock。在实际下载的过程中,并不是一次把所有的东西下载完,而是每次下载固定size的一段Di。所以每个DownloadBlock又会分成n段。
- 为每个DownloadBlock申请一个线程DownloadThread。其主要作用就是每次下载一段Di,并将其写入到文件中。
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