用过迅雷的同学都知道,迅雷有个功能叫做多线程,还有一个叫离线下载,我们这里重点介绍一下多线程下载。多线程,顾名思义就是很多歌线程同时在运行,为什么要提出多线程这个概念呢?因为有时候一个线程下载太慢了。举个例子,比如小时候常做的数学题,一个人挖沟需要15天,那么两个人对着挖呢?
当然数学题上面两个人的效率是不一样的,我们这里把这个问题简化了一下,只是想大家明白,什么是多线程,为什么有多线程。在多线程上出现过一个问题,为什么有要提出多线程?其实提出多线程是为了充分利用CPU的硬件资源,解决应用程序等待的问题。多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
2、思路
(1)获取网络连接
(2)初始化多线程下载信息,开始下载
(3)开辟硬盘空间,用于存放数据资源
(4)把从网络获取的数据放入申请的空间中
(5)下载完毕,关闭资源链接
给出一个下载400M电影的示意图,如下所示:
RandomAccessFile支持随机的访问
HTTP的Range头字段指定每个线程从文件的什么位置开始下载。
3、代码解析
3.1 设置需要下载文件的信息
1
2
3
4
5
|
RandomAccessFile randOut =
new
RandomAccessFile(
this
.saveFile,
"rwd"
);
if
(
this
.fileSize>
0
){
randOut.setLength(
this
.fileSize);
//设置文件的大小
}
randOut.close();
//关闭该文件,使设置生效
|
1
2
3
4
5
6
7
8
|
URL url =
new
URL(
this
.downloadUrl);
if
(
this
.data.size() !=
this
.threads.length){
//如果原先未曾下载或者原先的下载线程数与现在的线程数不一致
this
.data.clear();
//将数据置空
for
(
int
i =
0
; i <
this
.threads.length; i++) {
//遍历线程池
this
.data.put(i+
1
,
0
);
//初始化每条线程已经下载的数据长度为0
}
this
.downloadedSize =
0
;
//设置已经下载的长度为0
}
|
3.3 开始下载文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
for
(
int
i =
0
; i <
this
.threads.length; i++) {
//通过特定的线程ID获取该线程已经下载的数据长度
int
downloadedLength =
this
.data.get(i+
1
);
//判断线程是否已经完成下载,否则继续下载
if
(downloadedLength <
this
.block &&
this
.downloadedSize <
this
.fileSize){
//初始化特定id的线程
this
.threads[i] =
new
DownloadThread(
this
, url,
this
.saveFile,
this
.block,
this
.data.get(i+
1
), i+
1
);
//设置线程的优先级,Thread.NORM_PRIORITY = 5 Thread.MIN_PRIORITY = 1 Thread.MAX_PRIORITY = 10
this
.threads[i].setPriority(
7
);
//启动线程
this
.threads[i].start();
}
else
{
this
.threads[i] =
null
;
//表明在线程已经完成下载任务
}
}
|
1
2
|
fileService.delete(
this
.downloadUrl);
//如果存在下载记录,删除它们,然后重新添加
fileService.save(
this
.downloadUrl,
this
.data);
//把已经下载的实时数据写入数据库
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
boolean
notFinished =
true
;
//下载未完成
// 循环判断所有线程是否完成下载
while
(notFinished) {
notFinished =
false
;
//假定全部线程下载完成
for
(
int
i =
0
; i <
this
.threads.length; i++){
if
(
this
.threads[i] !=
null
&& !
this
.threads[i].isFinished()) {
//如果发现线程未完成下载
notFinished =
true
;
//设置标志为下载没有完成
//如果下载失败,再重新在已经下载的数据长度的基础上下载
if
(
this
.threads[i].getDownloadedLength() == -
1
){
//重新开辟下载线程,代码与上面一致
}
}
}
if
(listener!=
null
){
listener.onDownloadSize(
this
.downloadedSize);
}
//通知目前已经下载完成的数据长度
}
//下载完成删除记录
if
(downloadedSize ==
this
.fileSize){
fileService.delete(
this
.downloadUrl);
}
|
断点续传是说在下载的时候,我们因为某些原因,导致了下载的暂停,比如在电脑上,我们的电脑突然断电了,手机上的网络中断了,都会导致当前的下载任务终止,那么当我们再次回来的时候,程序应该是可以继续下载的,不然前面下载的资源就都浪费了。
根据上面的描述,我们应该可以知道,实现断点续传,关键是实现下载的数据存储在数据库中,等到之后我们程序再次进入的时候,会到数据库中去查询一下数据,然后接着继续下载。而存储数据到数据库并不是太复杂,难的是如何识别程序的哪些数据被下载了,哪些数据是没有下载的,这里,我们在下载的时候使用了下载的线程id做识别。
如果该线程id的数据没有被完整下载,应该是不会存储到数据库的,那么这一部分的数据就要重新下载,在下载完成之后,数据拼接起来就是一个完整的文件了。