首页 > 代码库 > java中多线程下载

java中多线程下载

多线程下载可以抢占其它相同优先级用户的网络资源(宽带),所以说下载速度比较快,迅雷、快播都使用了多线程下载。
1.请求服务器上的文件的长度
2.根据服务器上的文件长度在手机上创建一个一模一样大小的文件
3.根据线程的个数和文件的长度来计算每一个线程需要下载的范围
           文件的长度为:10  线程的数量为:3    每一块的大小:10/3=3.3333=3
            线程0:0~2
            线程1:3~5
            线程2:6~9
                
                    开始的位置:线程的id*每一块的大小。
                    结束的位置:线程的(id+1 ) *每一块的大小 -1;
                     最后一个线程结束的位置是:文件的长度-1;   

4.开启多个线程,每一个线程下载自己的那块范围
    1).请求服务器上传的部分内容。
                Range(请求头): bytes=3-8获取服务器文件3-8的内容
                特点:服务器返回值不再是200,是206.
    2).把写入的位置移动到线程开始的位置上。
    3).循环的读取内容写入到本地。


####断点下载(非正常关闭)
*在每一次读取到数据,写入本地数据时,应该把总记录存到本地。
*当下一次开始下载时,先查看有没有下载的记录(存档),如果有就取出来,继续下载

代码:

public class DownloadUtils {
	// 完成的线程
	private int completeThread;
	private int threadCount;

	/**
	 * 开始多线程下载
	 * 
	 * @param url
	 *            访问的服务器地址
	 * @param dir
	 *            本地接收路径
	 * @param threadCount
	 *            开启多少条线程下载
	 */
	public void startMultiThreadDownload(final String url, final String dir,
			final int threadCount) {
		this.threadCount = threadCount;
		completeThread = 0;
		new Thread(new Runnable() {

			@Override
			public void run() {
				// 1.请求服务器上的文件的长度
				int fileLength = getRemoteServiceFileLength(url);
				if (fileLength == -1) {
					System.out.println("请求服务器文件长度失败,停止下载");
					return;
				}
				System.out.println("远程服务器的长度为:" + fileLength);

				// 2.根据服务器上的文件长度在手机上创建一个一模一样大小的文件
				String fileName = url.substring(url.lastIndexOf("/") + 1);
				File file = new File(dir, fileName);
				try {
					RandomAccessFile raf = new RandomAccessFile(file, "rwd");
					raf.setLength(fileLength);
					raf.close();
					System.out.println("本地文件创建成功" + file.getPath());
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				// 3.根据线程的个数和文件的长度来计算每一个线程需要下载的范围
				int blockSize = fileLength / threadCount;
				for (int i = 0; i < threadCount; i++) {
					// 开始的位置:线程的id*每一块的大小。

					int start = i * blockSize;
					// 结束的位置:线程的(id+1 ) *每一块的大小 -1;
					int end = (i + 1) * blockSize - 1;
					// 最后一个线程结束的位置是:文件的长度-1;
					if ((i == threadCount - 1)) {
						end = fileLength - 1;
					}
					// 4.开启多个线程,每一个线程下载自己的那块范围,传递参数:下载地址,存储文件的路径
					// 开始的位置,结束的位置,线程ID
					new Thread(new DownloadRunnable(url, file.getPath(), start,
							end, i)).start();

				}

			}
		}).start();

	}

	/**
	 * 下载任务类
	 * 
	 * @author Administrator
	 * 
	 */
	class DownloadRunnable implements Runnable {
		private String url;
		private File localFile; // 文件的路径
		private int start; // 当前线程开始下载的位置
		private int end; // 结束下载的位置
		private int threadID;// 线程ID

		public DownloadRunnable(String url, String path, int start, int end,
				int i) {
			this.url = url;
			this.localFile = new File(path);
			this.start = start;
			this.end = end;
			this.threadID = i;
		}

		@Override
		public void run() {
			HttpURLConnection conn = null;

			try {
				// 添加断点下载
				int total = 0;// 当前线程下载的总进度
				// 把总进度值存储到本地去
				File cacheFile = new File(localFile.getParent(), "thread"
						+ threadID + ".hm");

				RandomAccessFile cacheRAF = new RandomAccessFile(cacheFile,
						"rwd");

				// 判断上一次是否有缓存的进度
				if (cacheFile.exists() && cacheFile.length() > 0) {// 文件存在并且有数据
					// 上一次下载的进度
					int progress = Integer.valueOf(cacheRAF.readLine());
					start += progress;// 把上一次的进度在开始的位置加上,继续下载
					total = progress;// 把上一次下载总进度赋值
				}
				System.out.println("线程" + threadID + ",开始下载了,下载的范围是:" + start
						+ " ~" + end);
				conn = (HttpURLConnection) new URL(url).openConnection();
				conn.setRequestMethod("GET");
				conn.setConnectTimeout(5000);
				conn.setReadTimeout(5000);
				// 设置请求头消息:Range,请求服务器上文件的部分内容
				conn.setRequestProperty("Range", "bytes=" + start + "-" + end);

				int responseCode = conn.getResponseCode();

				if (responseCode == 206) {
					int contentLength = conn.getContentLength();
					System.out.println("线程" + threadID + ",请求成功,内容长度为:"
							+ contentLength);
					// 得到服务器返回的数据
					InputStream is = conn.getInputStream();
					RandomAccessFile raf = new RandomAccessFile(localFile,
							"rwd");
					// 把写入的位置移动到线程开始的位置上
					raf.seek(start);
					byte[] buf = new byte[1024];
					int len = 0;

					while ((len = is.read(buf)) != -1) {
						raf.write(buf, 0, len);
						total += len;
						cacheRAF.seek(0);// 每一次都移动到文件的开始位置进行写入
						cacheRAF.write(String.valueOf(total).getBytes());

					}
					cacheRAF.close();
					raf.close();
					is.close();
					System.out.println("线程" + threadID + ",下载完成了.");
					completeThread++;
					if (completeThread == threadCount) {
						System.out.println("全部下载完整,删除临时配置文件.");
						for (int i = 0; i < threadCount; i++) {
							File deleteFile = new File(localFile.getParent(),
									"thread" + i + ".hm");
							System.out.println(deleteFile.delete());
						}
					}

				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();

			} finally {
				if (conn != null) {
					conn.disconnect();
				}
			}

		}
	}

	/**
	 * 根据URL连接请求远程服务器获取文件大小
	 * 
	 * @param url
	 *            服务器地址
	 * @return
	 */
	private int getRemoteServiceFileLength(String url) {
		HttpURLConnection conn = null;
		try {
			conn = (HttpURLConnection) new URL(url).openConnection();
			// 请求方式,必须大小
			conn.setRequestMethod("GET");
			// 连接超时
			conn.setConnectTimeout(5000);
			// 读取超时
			conn.setReadTimeout(5000);

			int reponseCode = conn.getResponseCode();// 获取的服务器返回的响应码
			if (reponseCode == 200) {
				return conn.getContentLength();
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (conn != null) {
				conn.disconnect();
			}
		}

		return -1;
	}
}


调用:
public class Demo {
	public static void main(String[] args) {
		System.out.println("开启多线程下载了............");

		// 链接,路径,线程的数量
	
		String url = "http://192.168.1.109/FeiQ.exe";
		String dir = "F:/WDJDownload";
		int threadCount = 3;
		DownloadUtils du = new DownloadUtils();
		du.startMultiThreadDownload(url, dir, threadCount);
	}
}