- 百度云下载速度慢,但一开通svip后速度就会提上去一些;这是因为百度云解除限速,同时给你使用多线程进行下载,我们今天就来看一看多线程下载的原理。
- 线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开启好几个下载通道
- 也就是说,多线程可以将 一个文件拆分为好几块 这好几块用不同的线程进行下载 自然下载速度变快,但不是线程越多越好,线程太多会导致电脑cpu资源分配耗时太多从而导致速度更慢
举个例子
一个1000byte的文件 你可以从头下到尾巴 也可以分成三块进行下载
Thread1:0-333 thread2:334-666 thread:666-1000
其中的问题是最后一个线程要下载后面的尾巴~
下面我们进行代码部分:
一、整体思考
- 定义 downloadManager类 -> 进行下载管理
- 定义 downloadOperation类 -> 进行下载的整体工作
1.从服务器获取文件的大小
2.在本地创建同等大小的文件 用于保存下载的数据
3.派遣线程进行数据下载 - 定义 downloadThread类 -> 进行线程下载 实现具体下载过程
- 最后调用downloadManager类进行下载
- downloadManager -> downloadOperation -> downloadThread
一、downloadManager
- 为了熟练单例设计模式,我们采用该方法进行类的创建
public class DownloadManager {
private Map<String,String>[] source;
private static DownloadManager manager;
private static Object obj = new Object();
private DownloadManager() { }
public static DownloadManager getInstance(){
if (manager == null){
synchronized (obj){
if (manager == null);
manager = new DownloadManager();
}
}
return manager;
}
// 多线程下载多个文件
public void loadData(Map<String,String>[] datas){
}
// 多线程下载一个文件
public void loadData(String urlString, String filePath, int threadCount){
final DownloadOperation downloader = new DownloadOperation(urlString,filePath,threadCount);
downloader.Download();
// 调用一个线程实现状态监听
new Thread(new Runnable() {
@Override
public void run() {
while (downloader.currentRate() < 1){
System.out.println("当前下载进度进度:"+downloader.currentRate());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
二、downloadOperation
- 1.给上相应成员变量
- 2.创建构造方法
- 3.创建Download下载方法 -> 使用多线程进行下载
3.1获取文件的大小
3.2创建文件 用于保存下载的数据
3.3派遣线程进行数据下载
public class DownloadOperation {
private URL url;
private String filePath;
private int threadCount;
private long size;
private DownloadThread[] tasks;
public DownloadOperation(String urlString, String filePath, int threadCount) {
try {
this.url = new URL(urlString);
} catch (Exception e) {
e.printStackTrace();
}
this.filePath = filePath;
this.threadCount = threadCount;
tasks = new DownloadThread[threadCount];
}
public void Download(){
// 获取文件的大小
getFileSize();
System.out.println(size);
// 创建文件 用于保存下载的数据
createFile();
// 派遣线程进行数据下载
dispatch();
}
/**
* 使用head获取头部信息 获取资源大小
*/
private void getFileSize() {
// head请求只获取头部信息 不去做具体操作
// url
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("HEAD");
// 获取资源大小
size = conn.getContentLengthLong();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭连接
conn.disconnect();
}
}
/**
* 在本地创建文件
*/
private void createFile(){
File file = new File(filePath);
RandomAccessFile rac = null;
// 创建文件
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// 设置文件大小
try {
rac = new RandomAccessFile(file,"rw");
rac.setLength(size);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
rac.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 派遣线程进行下载
*/
private void dispatch(){
// 计算每个线程下载的平均大小
long average = size/threadCount;
long startIndex = 0;
long endIndex = 0;
long downloadSize = 0;
for (int i = 0; i < threadCount; i++) {
startIndex = i * average;
endIndex = (i+1)*average - 1;
// 最后一个线程下载后面全部的
if ( i == (threadCount - 1)){
endIndex = size - 1;
}
// 创建线程
DownloadThread dt = new DownloadThread(url,filePath,startIndex,endIndex,i);
// 保存这个线程对象
tasks[i] = dt;
// 启动下载
dt.start();
}
}
/**
* 进行进度监控
* @return
*/
public float currentRate(){
long len = 0;
for (DownloadThread dt: tasks){
len += dt.currentDownloadLength;
}
return (float)len/size;
}
}
四、downloadThread类
具体步骤
1.使用URL对象的 url.openConnection()方法 获取HttpURLConnection 链接对象
2.设置响应参数 ,发送get请求 设置链接网络的超时时间
3.获取随机读写对象
RandomAccessFile raf = new RandomAccessFile(filePath,"rw");4.设置一个请求头Range (作用告诉服务器每个线程下载的开始位置和结束位置)
使用URLConnction对象conn的 setRequestProperty() 方法
conn.setRequestProperty("Range", "bytes="+起点索引值+"-"+终点索引值);5.获取服务器返回的Http状态码
int code = conn.getResponseCode(); //200 代表获取服务器资源全部成功 206请求部分资源 成功
具体code码百度一搜即可
- 6.如果返回码可以,则进行数据传输
6.1获取输入流
6.2开始读取数据 写入到文
6.3其他相应细节
public class DownloadThread extends Thread{
private URL url;
private String filePath;
private long startIndex;
private long endIndex;
private int threadId;
public long currentDownloadLength = 0;
public DownloadThread(URL url, String filePath, long startIndex, long endIndex, int threadId) {
this.url = url;
this.filePath = filePath;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
@Override
public void run() {
// 定位文件到这个线程应该写入的位置
try {
// 1.获取HttpURLConnection 链接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 2.设置参数 发送get请求 设置链接网络的超时时间
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setConnectTimeout(5*5000);
// 3.获取随机读写对象
RandomAccessFile raf = new RandomAccessFile(filePath,"rw");
raf.seek(startIndex);
// 4.设置一个请求头Range (作用告诉服务器每个线程下载的开始位置和结束位置)
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
// 5.获取服务器返回的Http状态码
int code = conn.getResponseCode(); //200 代表获取服务器资源全部成功 206请求部分资源 成功
if (code == 206) {
// 3.获取输入流
InputStream is = conn.getInputStream();
// is.skip(startIndex) 从这个地方开始下载 与 上面同样的作用
// 6.开始读取数据 写入到文件
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
raf.write(buffer, 0, len);
// 记录当前下载的长度
currentDownloadLength += len;
}
raf.close();
is.close();
conn.disconnect();
} else{
System.out.println("数据请求失败");
}
// System.out.println("线程id:"+threadId + "---下载完毕了,共下载字节数:" + currentDownloadLength + "byte");
} catch (Exception e) {
e.printStackTrace();
}
}
}