实现了一个基于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

Java多线程的下载器(1)的更多相关文章

  1. Android开发多线程断点续传下载器

    使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点 ...

  2. 我的Android进阶之旅------>Android基于HTTP协议的多线程断点下载器的实现

    一.首先写这篇文章之前,要了解实现该Android多线程断点下载器的几个知识点 1.多线程下载的原理,如下图所示 注意:由于Android移动设备和PC机的处理器还是不能相比,所以开辟的子线程建议不要 ...

  3. 用 python 实现一个多线程网页下载器

    今天上来分享一下昨天实现的一个多线程网页下载器. 这是一个有着真实需求的实现,我的用途是拿它来通过 HTTP 方式向服务器提交游戏数据.把它放上来也是想大家帮忙挑刺,找找 bug,让它工作得更好. k ...

  4. Python实现多线程HTTP下载器

    本文将介绍使用Python编写多线程HTTP下载器,并生成.exe可执行文件. 环境:windows/Linux + Python2.7.x 单线程 在介绍多线程之前首先介绍单线程.编写单线程的思路为 ...

  5. java多线程断点下载原理(代码实例演示)

    原文:http://www.open-open.com/lib/view/open1423214229232.html 其实多线程断点下载原理,很简单的,那么我们就来先了解下,如何实现多线程的断点下载 ...

  6. 实现android支持多线程断点续传下载器功能

    多线程断点下载流程图: 多线程断点续传下载原理介绍: 在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度手机端下载数据时难免会出现无信号断线.电量不足等情况,所以需要断点续传功能根据下 ...

  7. java 多线程断点下载功能

    import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.Rand ...

  8. Java多线程断点下载文件

    Java实现断点续传+多线程下载 如下代码所示,每一步都有注解 思路: 通过URL连接到服务器上要下载的文件,得到文件的大小: 算出每条线程下载的开始位置和结束位置,例如,有两条线程下载100Byte ...

  9. java多线程批量下载文件

    多线程下载文件 平时开发中有时会用到文件下载,为了提高文件的下载速率,采用多线程下载能够达到事半功倍的效果: package test; /** * 文件下载类 * @author luweichen ...

随机推荐

  1. 开发常见错误之 : Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar

    SLF4J: Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar on the class path, preempting StackO ...

  2. Jenkins常见任务配置

    一.pmd 二.checkstyle -DskipTests=true clean compile package -Dcheckstyle.config.location="custom- ...

  3. [Sdoi2016]生成魔咒[SAM or SA]

    4516: [Sdoi2016]生成魔咒 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1017  Solved: 569[Submit][Statu ...

  4. 栈和队列的基础算法学习(EPI)

    今天学习的时间虽然挺多的,但是总觉效率不高.其实今天没有按照计划进行EPI题目的浏览,白天去看了其他的书籍.准备找工作可能需要的状态是一定量经典的书,偶尔温习才可.书是看不完的,知识点也是固定的.所以 ...

  5. Egret 中实现3种状态切换按钮

    一.游戏中的常用3种状态按钮 Egret种提供了2种状态切换的按钮ToggleButton. 但是在游戏中常用到3种状态的按钮,比如任务系统的领取.已领取.未领取. 比如下图中宝箱的打开.浏览后打开. ...

  6. C#取得Web程序和非Web程序的根目录的N种取法

    取得控制台应用程序的根目录方法方法1.Environment.CurrentDirectory 取得或设置当前工作目录的完整限定路径方法2.AppDomain.CurrentDomain.BaseDi ...

  7. [工具] Textify – 复制不可能的窗口内容[Win]

    Textify 是一款 Windows 下的小工具,能够复制那些平时无法复制的内容,比如错误提示.菜单按钮文字等等,只需要按下快捷键就可以随意复制,俗称复制不可能. http://rammichael ...

  8. html5播放器制作小结

    链接:http://snowinmay.net/6rooms/html/music.php 9月份前的版本: 播放,暂停,点赞,播放状态显示. 9.2版本: 下载歌曲,静音,时间倒计时(点击暂停时倒计 ...

  9. Python多线程应用示例

    实现任务描述如下: 创建多个子线程,共同访问一个队列中的元素,并执行相应操作.要求要按照元素的执行要按照队列顺序,并且元素的执行不能有重复. 示例代码如下: #simple sample to sho ...

  10. MUI---IOS切换到后台继续播放音乐

    应用切换到后台继续音乐播放HBuilder默认生成的应用在iOS是不支持后台音乐播放的,当应用切换到后台时音乐将暂停播放,下次切换到前台继续播放.如果要支持应用切换到后台后继续播放音乐功能需要进行额外 ...