基于Java多线程的下载器源码剖析.docx
- 文档编号:17916123
- 上传时间:2023-08-04
- 格式:DOCX
- 页数:35
- 大小:513.34KB
基于Java多线程的下载器源码剖析.docx
《基于Java多线程的下载器源码剖析.docx》由会员分享,可在线阅读,更多相关《基于Java多线程的下载器源码剖析.docx(35页珍藏版)》请在冰点文库上搜索。
基于Java多线程的下载器源码剖析
基于Java多线程的下载器源码剖析
(一)
分类:
JavaHYPERLINK"21:
29 747人阅读 评论(6) 收藏 举报
多线程HYPERLINK"""""
目录(?
)[+]
本文实现了一个基于Java多线程的下载器,可提供的功能有:
1.对文件使用多线程下载,并显示每时刻的下载速度。
2.对多个下载进行管理,包括线程调度,内存管理等。
这篇文章的结构如下:
首先讨论如何实现利用Java多线程对单个文件进行下载。
然后讨论当系统中有多个文件下载,如何对这些下载进行管理。
包括线程调度,内存管理等。
一:
单个文件下载的管理
1.单文件下载类层次
首先简要介绍一下单个文件下载管理的类层次:
来一张图来表示。
∙为需要下载的文件创建一个Download类,Download负责管理该文件下载时的线程管理、文件管理、当前速度计算等操作。
∙根据线程的数目tNum,将该文件分为tNum段,每段为一个DownloadBlock。
在实际下载的过程中,并不是一次把所有的东西下载完,而是每次下载固定size的一段Di。
所以每个DownloadBlock又会分成n段。
∙为每个DownloadBlock申请一个线程DownloadThread。
其主要作用就是每次下载一段Di,并将其写入到文件中。
2.单文件下载
对于单个下载,步骤如下
∙连接资源服务器,获取资源信息,创建文件
∙ 切分资源,为每个线程分配固定的下载区域。
1)封装下载的属性
在建立下载之前,我们把每一次下载进行抽象封装。
首先把URL、目标文件等封装在一个DownloadConfig类中。
其中包含了4个属性:
[java] viewplainHYPERLINK"
∙private URL url; //文件下载地址
∙private File file; //下载文件保存目标文件
∙private int nthread; //下载该文件需要的线程数
∙private int priority; //该下载的优先级
如下如所示:
2)连接资源服务器,获取资源信息,创建文件,并指定文件大小
[java] viewplainHYPERLINK"
∙length = config.getUrl().openConnection().getContentLength();
∙RandomAccessFile file = new RandomAccessFile(config.getFile(), "rw");
∙file.setLength(length);
∙file.close();
3)切分资源,为每个线程分配固定的下载区域,并将当前的下载加入到队列中
[java] viewplainHYPERLINK"
∙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.创建缓存,创建连接。
设置获取资源数据的范围,创建文件,并设置写入位置
[java] viewplainHYPERLINK"
∙//创建缓存
∙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已经下载完毕,退出该线程。
[java] viewplainHYPERLINK"
∙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
其中key为每个block的start值,而value为该block已经下载完的段D。
在当前时刻,我们需要统计当前Download已经下载完成段D的数量,然后再和上一时刻的相比较,则可以得出当前的下载速度。
具体代码见下:
[java] viewplainHYPERLINK"
∙class CheckSpeedTask extends TimerTask{
∙
∙ private static final Log log = LogFactory.getLog(CheckSpeedTask.class);
∙
∙ private Download download;
∙ private ConcurrentHashMap
∙
∙ 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
∙ 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()函数中,计算当前下载的速度
[java] viewplainHYPERLINK"
∙while(activeThreads.size() > 0 || blockQueue.size() > 0){
∙ Thread.sleep(1000);
∙ checkSpeed();
∙}
上面的代码演示了如何使用Java多线程对单个文件进行下载,接下来我们继续讨论如何对多个下载进行调度、管理等
基于Java多线程的下载器源码剖析
(二)
分类:
JavaHYPERLINK"10:
36 571人阅读 评论(6) 收藏 举报
多线程HYPERLINK"""""
目录(?
)[+]
三:
多个文件下载的管理
这一节我们主要来讲一下如何对多个文件的下载进行管理
首先来看一下整个系统的UML图
从最下面开始说起:
Download代表一个下载类,对每一个文件都需要创建一个Download实例,用于对该文件下载线程的管理。
其中每个Download中都有以下几个对象:
[java] viewplainHYPERLINK"
∙private ConcurrentLinkedQueue
∙private ConcurrentLinkedQueue
∙private ConcurrentHashMap
∙private ConcurrentLinkedQueue
其中
∙blockQueue是一个队列,用于存储当前需要下载的DownloadBlock。
Download对文件进行切分形成的DownloadBlock会被放入到放入到blockQueue中,供以后的下载。
∙blockCache为block内存缓存池,主要是为了能够复用已经建立好的DownloadBlock。
∙blockCounts为一个Map,其中key为每个block的start值,而value为该block已经下载完的段D。
主要作用是统计出当前已经每个Block已经下载完的段D,以计算实时下载速度
∙activeThreads主要是为了存储该Thread中所有的活跃线程。
DownloadBlock是一个下载块,其里面有3个成员变量
[java] viewplainHYPERLINK"
∙private Download download; //其所属的Download
∙private long start; //下载文件起始处
∙private long length; //下载文件的长度
DownloadThread是指下载进程,每个DownloadBlock都需要启动一个DownloadThread去进行下载。
即
[java] viewplainHYPERLINK"
∙new DownloadThread(block).start()
DownloadDeamon为了一个守护线程。
其内部主要为了下载所有的需要下载DownloadBlock
[java] viewplainHYPERLINK"
∙private DownloadList downloads; //当前系统中所有的下载列表
∙private ExecutorService threadPool; //线程池
Downloader代表整个下载系统,整个系统中只有一个实例对象,因此我们需要保证系统中只有一个实例对象。
[java] viewplainHYPERLINK"
∙private DownloaderConfig config; // Downloader配置
∙private DownloadList downloads; //当前系统所有的下载列表private Thread deamon; //守护进程
∙private ConcurrentLinkedQueue
∙private Timer timer; //
看了上面一大堆的东西,我保证你现在很晕,OK,我们从使用的角度来看整个系统是如何运行的。
下面是示例代码。
[java] viewplainHYPERLINK"
∙public static void main(String[] args) {
∙
∙ Downloader downloader = Downloader.getInstance();
∙
∙ //下载第一个文件
∙ String url1 = "
∙ String saveFile1 = "data/tmsvm_src_v1.1.0.rar";
∙ DownloadConfig config = new DownloadConfig();
∙ try {
∙ config.setUrl(new URL(url1));
∙ config.setFile(new File(saveFile1));
∙ config.setNthread(new Integer(5));
∙ config.setPriority(new Integer(6));
∙ //将第一个下载加入到下载列表中
∙ downloader.addDownload(new Download(config, downloader.getTimer()));
∙ } catch (MalformedURLException e) {
∙ // TODO Auto-generated catch block
∙ e.printStackTrace();
∙ }
∙
∙ //下载第二个文件
∙ String url2 = "
∙ String saveFile2 = "data/Tmsvm参考文档(v1.1.0).rar";
∙ try {
∙
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 基于 Java 多线程 下载 源码 剖析
![提示](https://static.bingdoc.com/images/bang_tan.gif)