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: 0blocksize ~ 1locaksize-1
线程2 下载2个byte: 1blocksize ~ 2blocksize-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文件
打开工程的根目录,确认下载的文件大小是否和服务器上的文件大小一致