多线程下载
使用多线程下载文件可以很快地完成文件的下载,比如迅雷就是采用了多线程的方式来实现快速下载的。之所以采用多线程的形式会加快下载速度,是因为客户端启动多个线程进行下载时服务器就会为该客户端提供相应的服务,自然客户端就拥有了较快的下载速度。实际上并不是客户端并发的下载线程越多,程序下载速度就越快,,因为当客户端开启太多的并发线程之后,应用程序需要维护每条线程的开销,线程同步的开销,这些开销反而会导致下载速度降低。
在本小节中我们学习如何使用HttpURLConnection实现多线程下载,HttpURLConnection是URLConnection的子类。相对于URLConnection,它增加了一些用于操作HTTP资源的便捷方法,利用它可以向指定网站发送GET请求或POST请求。常用的HttpURLConnection类的方法有:
-
int getResponseCode() 获取服务器的响应代码
-
String getResponseMessage() 获取服务器的响应消息
-
String getRequestMethod() 获取发送请求的方法
-
void setRequestMethod(String method) 设置发送请求的方法
实现多线程下载的步骤:
-
创建URL对象;
-
利用HttpURLConnection类获取指定URL对象所指向资源的大小
-
在本地磁盘上创建一个与网络资源相同大小的空文件
-
计算第条线程应该下载网络资源的哪个部分(从哪个字节开始,到哪个字节结束)
-
依次创建、启动多条线程来下载网络资源的指定部分
示例:使用HttpURLConnection实现多线程下载。
第一步:创建一个Java项目,然后在其中创建一个名为DownLoaderTest.java的类,代码如下:
public class DownLoaderTest {
public static void main(String[] args) throws Exception{
String path = "http://jywt2.newhua.com/down/CPUCOOL9.zip";//下载一个压缩包
path ="http://wbsoft.wn51.com/wnwb_782_11.exe";//下载万能五笔
new DownLoaderTest().download(path);
}
// Http协议,从Internet上请求资源,并保存下来,实现断点下载
public void download(String path) throws Exception {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();//支持 HTTP 特定功能的 URLConnection
// 设置连接超时
conn.setConnectTimeout(6000);
conn.setRequestMethod("GET");
int filesize = conn.getContentLength();//获取要下载的文件的大小
//假设在程序中采用3条线程来下载数据
int threadSize =3;
//每条线程要下载的文件大小,即分给每条线程的文件长度
int block = filesize/3+1;
conn.disconnect();//关闭连接
String houzhui =path.substring(path.lastIndexOf('.'));
System.out.println(houzhui);
File file = new File("fileName"+houzhui);
for(int i=0 ; i<threadSize ; i++){//开始下载
int startPosition =i*block;//每条线程在下载网络文件时的开始位置
RandomAccessFile threadfile = new RandomAccessFile(file, "rw"); //RandomAccessFile可以从文件的任意位置写入数据
threadfile.seek(startPosition); //设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
new DownloadThread(i ,url, startPosition, threadfile, block).start();
}
byte[] quit = new byte[1];
System.in.read(quit);
while (!('q'==quit[0])) {
Thread.sleep(4000);
}
}
// 从流里面读数据
public byte[] readStream(InputStream inputStream) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();// 用来往内存中输入字节数据
// 创建一个新的 byte 数组输出流。缓冲区的容量最初是 32 字节,如有必要可增加其大小。
byte[] buffer = new byte[1024];
int lenth = -1;
while ((lenth = inputStream.read(buffer)) != -1) { // 如果因为已经到达流末尾而不再有数据可用,则返回
// -1
outputStream.write(buffer, 0, lenth);
}// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此 byte 数组输出流。
outputStream.close();
inputStream.close();
return outputStream.toByteArray();
}
private class DownloadThread extends Thread{
private URL url ;//下载路径
private int startPosition;//开始下载的位置
private RandomAccessFile threadFile ;//该线程要写入的文件
private int block;//该线程要负责的文件的大小
private int id ; //线程的编号
public DownloadThread (int id ,URL url , int startPosition,RandomAccessFile file ,int block){
this.id = id;
this.url = url;
this.startPosition = startPosition;
this.threadFile = file;
this.block = block;
}
@Override
public void run() {//实现线程对所属段的下载
try { //在内部类DownloadThread的run()方法负责打开远程资源的输入流
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Range", "bytes="+startPosition+"-");
conn.setConnectTimeout(6000);
InputStream inputStream = conn.getInputStream();
byte[] buffer = new byte[1024];
int lenth = -1;
int readFileSize = 0;
while ((lenth = inputStream.read(buffer)) != -1 && readFileSize <block) {
// 如果因为已经到达流末尾而不再有数据可用,则返回-1
threadFile.write(buffer, 0, lenth);//将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。
readFileSize +=lenth;//累计下载的文件的大小
}// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此 byte 数组输出流。
threadFile.close();
conn.disconnect();
System.out.println(id+ " 线程下载完");
} catch (IOException e) {
e.printStackTrace();
}
super.run();
}
}
}
第二步:运行程序,查看结果
多线程下载控制台输出效果图