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); //从文件的什么位置开始写入数据

下图为原理图:

iOS 多线程下载 mj 多线程下载apk_thread

准备工作:

搭建Tomcat服务器,放置需要下载的资源,如下图所示的video1.avi

iOS 多线程下载 mj 多线程下载apk_iOS 多线程下载 mj_02


确认资源文件的大小为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>




运行结果如下:

iOS 多线程下载 mj 多线程下载apk_iOS 多线程下载 mj_03


刷新工程目录,可以发现刚刚下载的temp.avi文件

iOS 多线程下载 mj 多线程下载apk_多线程_04


打开工程的根目录,确认下载的文件大小是否和服务器上的文件大小一致

iOS 多线程下载 mj 多线程下载apk_iOS 多线程下载 mj_05