1.为什么要使用多线程下载?
使用多线程下载文件可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多,例如:假设服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由cpu划分时间片轮流执行,如果A应用使用了99条线程下载文件,那么相当于占用99个用户资源,假设一秒内cpu分配给每条线程的平均时间是10ms,A应用在服务器中一秒内就得到了990ms的执行时间,而其他应用在一秒只有10ms的执行时间,就如同一个水龙头,每秒出水量相等的情况下,放水990毫秒的水.肯定比放10毫秒的水要多.
2.多线程下载的实现过程:
1)首先得到下载文件的长度,然后设置到本地文件
HttpURLConnection.getContentLength();
RandomAccessFile file = new RandomAccessFile("vedio1.avi","nw");
file.setLength(filesize);//设置文件的长度
2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置.如,文件长度为6M,线程为3个,那么每条线程下载的数据为2M.每条线程开始下载的位置如下:
int blocksize = length / threadcount 即:6M / 3Thread = 2M/Thread
线程1 下载2个byte: 0*blocksize ~ 1*locaksize-1
线程2 下载2个byte: 1*blocksize ~ 2*blocksize-1
线程3 下载2个byte: 2*blocksize ~ length-1
3)使用Http协议的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,例如指定
HttpURLConnection.setRequestProperty("Range","bytes=2097152-4194303");
4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据
RandomAccessFile threadFile = new RandomAccessFile("video1.avi","nw");
threadfile.seek(2097152); //从文件的什么位置开始写入数据
下图为原理图:
准备工作:
搭建Tomcat服务器,放置需要下载的资源,如下图所示的video1.avi
确认资源文件的大小为23,250,432byte
开始编写代码了.以下代码都是j2se的知识,因此可以直接创建一个j2se的工程来演示
<span style="font-family:Courier New;font-size:14px;">import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
/**
*
* @author Chenys
* 多线程下载文件
*
*/
public class MultiThreadDownload {
/**
* 线程的数量
*/
private static int threadCount = 3;
/**
* 每个下载区块的大小
*/
private static long blocksize;
public static void main(String[] args) throws IOException {
// 服务器文件的路径
String path = "http://192.168.0.102:8080/video1.avi";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
if (code == 200) {
// 得到服务器端返回的文件大小,单位byte,字节
long size = conn.getContentLength();
System.out.println("服务器文件的大小:" + size);
// 计算每个blocksize的大小
blocksize = size / threadCount;
/**
* 1.首先在本地创建一个文件大小跟服务器一模一样的空白文件,RandomAccessFile类的实例支持对随机访问文件的读取和写入
* 参数1:目标文件 参数2:打开该文件的访问模式,"r" 以只读方式打开 ,"rw" 打开以便读取和写入
*/
File file = new File("temp.avi");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.setLength(size);// 设置文件的大小
// 2.开启若干个子线程,分别下载对应的资源
for (int i = 1; i <= threadCount; i++) {
long startIndex = (i - 1) * blocksize; // 由于服务端下载文件是从0开始的
long endIndex = i * blocksize - 1;
if (i == threadCount) {
// 最后一个线程
endIndex = size - 1;
}
System.out.println("开启线程:" + i + "下载的位置" + startIndex + "~"+ endIndex);
new DownloadThread(path, i, startIndex, endIndex).start();
}
}
conn.disconnect();
}
/**
* 自定义下载线程
* @author Chenys
*
*/
private static class DownloadThread extends Thread {
private int threadId; // 线程id
private long startIndex; // 开始下载的位置
private long endIndex; // 结束下载的位置
private String path;
public DownloadThread(String path, int threadId, long startIndex,
long endIndex) {
this.path = path;
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
//设置http协议请求头: 指定每条线程从文件的什么位置开始下载,下载到什么位置为止
conn.setRequestProperty("Range", "bytes=" + startIndex + "-"+ endIndex);
int code = conn.getResponseCode();
System.out.println("服务器返回码code=" + code);// 如果是下载一部分资源,那么返回码是206
InputStream is = conn.getInputStream();
File file = new File("temp.avi");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
// 指定文件的下载起始位置
raf.seek(startIndex);
System.out.println("第" + threadId + "个线程写文件的开始位置"
+ String.valueOf(startIndex));
// 开始保存文件
int len = 0;
byte[] buff = new byte[1024];
while ((len = is.read(buff)) != -1) {
raf.write(buff, 0, len);
}
is.close();
raf.close();
System.out.println("线程" + threadId + "下载完毕了");
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
</span>
运行结果如下:
刷新工程目录,可以发现刚刚下载的temp.avi文件
打开工程的根目录,确认下载的文件大小是否和服务器上的文件大小一致