多线程下载
所谓多线程下载,就是将目标数据分成若干个段,每个线程负责请求、写入一段数据的下载方式,这种方式可以解决由于单线程运算速度不能发挥所有网络带宽导致的慢速,但并不会突破物理网络的最大速度。用java实现多线程下载不需要第三方框架或jar包,只是基本的网络请求和读写操作就可以完成。
用来测试的文件最好选择可执行文件,因为如果选择图片或视频等文件,即使传输过程中出现了小部分数据的错误,也难以排查。
步骤
计算每个线程需要下载多少数据,从多少开始,到多少结束,这需要请求到所有的数据,获取数据的长度,就可以根据线程的数量来分配目标数据了,得到之后就可以开启线程下载了
代码
try {
//发送目标文件的请求,拿到文件长度
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestMethod("GET");
if (conn.getResponseCode() == 200) {
//获取长度
int length = conn.getContentLength();
//获取文件名
String fileName = getFileNameFromPath(path);
//创建临时文件
File file = new File(fileName);
RandomAccessFile raf = new RandomAccessFile(file, "rwd");//这样文件就可以直接写到磁盘上,不经过缓冲区了。
raf.setLength(length);//设置文件的大小
//raf使命完成,关闭资源
raf.close();
//计算每个线程需要下载多少数据
int size = length / threadCount;
//用循环计算每个线程的下载区间,并开启线程下载
for (int i = 0; i < threadCount; i++) {
int startIndex = i * size;
int endIndex = (i + 1) * size - 1;
if (i == threadCount -1){//如果是最后一个,就把剩下的全部下载完!
endIndex = length-1;
}
Thread thread = new MyThread(i,startIndex,endIndex);
thread.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
其中文件输出流使用RandomAccessFile的原因是能够直接将数据写入到磁盘上而不是先写入缓冲区在适当时再写入磁盘。
Java帮助文档原文:
"rws" 和 "rwd"
类的 force(boolean)
方法,分别传递 true 和 false 参数,除非它们始终应用于每个 I/O 操作,并因此通常更为高效。如果该文件位于本地存储设备上,那么当返回此类的一个方法的调用时,可以保证由该调用对此文件所做的所有更改均被写入该设备。这对确保在系统崩溃时不会丢失重要信息特别有用。如果该文件不在本地设备上,则无法提供这样的保证。
其中用for循环得到每个线程请求的目标数据信息后,开始自定义的线程对象实例,这个自定义线程对象就得到了请求数据的信息,可以在线程对象的run方法中使用了。
自定义的Thread代码
static class MyThread extends Thread{
private int tid, startIndex,endIndex;
public MyThread(int tid, int startIndex, int endIndex) {
this.tid = tid;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(8000);
conn.setConnectTimeout(8000);
conn.setRequestMethod("GET");
//设置请求的数据区间
conn.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);
if (conn.getResponseCode() == 206){//请求部分数据成功的响应码是206
InputStream is = conn.getInputStream();
int len;
byte[] buf = new byte[1024];
File file = new File(getFileNameFromPath(path));
RandomAccessFile raf = new RandomAccessFile(file,"rwd");
raf.seek(startIndex);//设置输出流写入的开始位置
while ((len = is.read(buf))!=-1){
raf.write(buf,0,len);
}
is.close();
raf.close();
System.out.println("第"+tid+"下载完毕");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
其中需要注意 Http请求部分部分数据成功得到的响应式206,并不是200。
其中必须要在连接中声明要请求的数据的区间,也要声明输出流写入文件的位置。