多线程断点下载

多线程断点下载

  1. 多线程下载
public class MultiThreadDownloader {
	private URL url;        // 目标地址
	private File file;        // 本地文件
	private long threadLen;    // 每个线程下载多少
	private static final int THREAD_AMOUNT = 3;                // 线程数
	private static final String DIR_PATH = "F:/Download";    // 下载目录
	public MultiThreadDownloader(String address) throws IOException {    
		url = new URL(address);                                                            // 记住下载地址
		file = new File(DIR_PATH, address.substring(address.lastIndexOf("/") + 1));        // 截取地址中的文件名, 创建本地文件
		// 创建一个临时文件路径
	}
	public void download() throws IOException {
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setConnectTimeout(3000);
		long totalLen = conn.getContentLength();                    // 获取文件总长度
		threadLen = (totalLen + THREAD_AMOUNT - 1) / THREAD_AMOUNT;    // 计算每个线程要下载的长度
		// 总长度 如果能整除 线程数, 每条线程下载的长度就是 总长度 / 线程数
		// 总长度 如果不能整除 线程数, 那么每条线程下载长度就是 总长度 / 线程数 + 1
		RandomAccessFile raf = new RandomAccessFile(file, "rw");    // 在本地创建一个和服务端大小相同的文件
		raf.setLength(totalLen);                                    // 设置文件的大小, 写入了若干个0
		raf.close();
		// 创建临时文件
		for (int i = 0; i < THREAD_AMOUNT; i++)        // 按照线程数循环
			new DownloadThread(i).start();            // 开启线程, 每个线程将会下载一部分数据到本地文件中
	}
	private class DownloadThread extends Thread {
		private int id;     // 用来标记当前线程是下载任务中的第几个线程
		public DownloadThread(int id) {
			this.id = id;
		}
		public void run() {
			// 从临时文件读取当前线程已完成的进度
			long start = id * threadLen;                    // 起始位置
			long end = id * threadLen + threadLen - 1;        // 结束位置
			System.out.println("线程" + id + ": " + start + "-" + end);
			try {
				HttpURLConnection conn = (HttpURLConnection) url.openConnection();
				conn.setConnectTimeout(3000);
				conn.setRequestProperty("Range", "bytes=" + start + "-" + end);    // 设置当前线程下载的范围(start和end都包含)
				InputStream in = conn.getInputStream();                            // 获取连接的输入流, 用来读取服务端数据
				RandomAccessFile raf = new RandomAccessFile(file, "rw");        // 随机读写文件, 用来向本地文件写出
				raf.seek(start);                                                // 设置保存数据的位置
				byte[] buffer = new byte[1024 * 100];    // 每次拷贝100KB
				int len;
				while ((len = in.read(buffer)) != -1) {
					raf.write(buffer, 0, len);            // 从服务端读取数据, 写到本地文件
					// 存储当前下载进度到临时文件
				}
				raf.close();
				System.out.println("线程" + id + "下载完毕");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws IOException {
		new MultiThreadDownloader("http://192.168.1.240:8080/14.Web/android-sdk_r17-windows.zip").download();
	}
}
  1. 断点下载
public class BreakpointDownloader {
	private static final String DIR_PATH = "F:/Download";    // 下载目录
	private static final int THREAD_AMOUNT = 3;                // 总线程数
	private URL url;            // 目标下载地址
	private File dataFile;        // 本地文件
	private File tempFile;        // 用来存储每个线程下载的进度的临时文件
	private long threadLen;        // 每个线程要下载的长度
	private long totalFinish;    // 总共完成了多少
	private long totalLen;        // 服务端文件总长度
	private long begin;            // 用来记录开始下载时的时间
	public BreakpointDownloader(String address) throws IOException {    
		url = new URL(address);                                                            // 记住下载地址
		dataFile = new File(DIR_PATH, address.substring(address.lastIndexOf("/") + 1));    // 截取地址中的文件名, 创建本地文件
		tempFile = new File(dataFile.getAbsolutePath() + ".temp");                        // 在本地文件所在文件夹中创建临时文件
	}
	public void download() throws IOException {
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setConnectTimeout(3000);
		totalLen = conn.getContentLength();                                    // 获取服务端发送过来的文件长度
		threadLen = (totalLen + THREAD_AMOUNT - 1) / THREAD_AMOUNT;            // 计算每个线程要下载的长度
		if (!dataFile.exists()) {                                            // 如果本地文件不存在
			RandomAccessFile raf = new RandomAccessFile(dataFile, "rws");    // 在本地创建文件
			raf.setLength(totalLen);                                        // 设置文件的大小和服务端相同
			raf.close();
		}
		if (!tempFile.exists()) {                                            // 如果临时文件不存在
			RandomAccessFile raf = new RandomAccessFile(tempFile, "rws");    // 创建临时文件, 用来记录每个线程已下载多少
			for (int i = 0; i < THREAD_AMOUNT; i++)                            // 按照线程数循环
				raf.writeLong(0);                                            // 写入每个线程的开始位置(都是从0开始)
			raf.close();
		}
		for (int i = 0; i < THREAD_AMOUNT; i++)    // 按照线程数循环
			new DownloadThread(i).start();        // 开启线程, 每个线程将会下载一部分数据到本地文件中
		begin = System.currentTimeMillis();        // 记录开始时间
	}
	private class DownloadThread extends Thread {
		private int id;     // 用来标记当前线程是下载任务中的第几个线程
		public DownloadThread(int id) {
			this.id = id;
		}
		public void run() {
			try {
				RandomAccessFile tempRaf = new RandomAccessFile(tempFile, "rws");        // 用来记录下载进度的临时文件
				tempRaf.seek(id * 8);                        // 将指针移动到当前线程的位置(每个线程写1个long值, 占8字节)
				long threadFinish = tempRaf.readLong();        // 读取当前线程已完成了多少
				synchronized(BreakpointDownloader.this) {    // 多个下载线程之间同步
					totalFinish += threadFinish;            // 统计所有线程总共完成了多少
				}
				long start = id * threadLen + threadFinish;        // 计算当前线程的起始位置
				long end = id * threadLen + threadLen - 1;        // 计算当前线程的结束位置
				System.out.println("线程" + id + ": " + start + "-" + end);
				HttpURLConnection conn = (HttpURLConnection) url.openConnection();
				conn.setConnectTimeout(3000);
				conn.setRequestProperty("Range", "bytes=" + start + "-" + end);        // 设置当前线程下载的范围
				InputStream in = conn.getInputStream();                                // 获取连接的输入流
				RandomAccessFile dataRaf = new RandomAccessFile(dataFile, "rws");    // 装载数据的本地文件(可以理解为输出流)
				dataRaf.seek(start);                                                // 设置当前线程保存数据的位置
				byte[] buffer = new byte[1024 * 100];            // 每次拷贝100KB
				int len;
				while ((len = in.read(buffer)) != -1) {
					dataRaf.write(buffer, 0, len);                // 从服务端读取数据, 写到本地文件
					threadFinish += len;                        // 每次写入数据之后, 统计当前线程完成了多少
					tempRaf.seek(id * 8);                        // 将临时文件的指针指向当前线程的位置
					tempRaf.writeLong(threadFinish);            // 将当前线程完成了多少写入到临时文件
					synchronized(BreakpointDownloader.this) {    // 多个下载线程之间同步
						totalFinish += len;                        // 统计所有线程总共完成了多少
					}
				}
				dataRaf.close();
				tempRaf.close();
				System.out.println("线程" + id + "下载完毕");
				if (totalFinish == totalLen) {                    // 如果已完成长度等于服务端文件长度(代表下载完成)
					System.out.println("下载完成, 耗时: " + (System.currentTimeMillis() - begin));
					tempFile.delete();                            // 删除临时文件
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws IOException {
		new BreakpointDownloader("http://192.168.1.240:8080/14.Web/android-sdk_r17-windows.zip").download();
	}
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏DOTNET

.NET MongoDB Driver GridFS 2.2原理及使用示例

一、API解读 1 GridFSBucketOptions 1)public string BucketName { get; set; } 获取或设置buck...

3478
来自专栏Java技术栈

Java程序员注意:Tomcat Get请求的巨坑!

2.6K2
来自专栏大内老A

通过扩展让ASP.NET Web API支持JSONP

同源策略(Same Origin Policy)的存在导致了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝。同源策略以及跨域...

1877
来自专栏后端云

支持windows虚拟机和linux虚拟机分区调度

用该image生成的vm,没有被nova-scheduler调度到windows主机组。

1114
来自专栏腾讯云Elasticsearch Service

Elasitcsearch 底层系列 Lucene 内核解析之 Stored Fields

Lucene 的 stored fields 主要用于行存文档需要保存的字段内容,每个文档的所有 stored fields 保存在一起,在查询请求需要返回字段...

1711
来自专栏cnblogs

knockoutjs 上自己实现的flux

在knockoutjs 上实现 Flux 单向数据流 状态机,主要解决多个组件之间对数据的耦合问题。 一、其实简单 flux的设计理念和实现方案,很大程度上人借...

2208
来自专栏文武兼修ing——机器学习与IC设计

队列及其实现队列队列的实现

队列 队列即FIFO,一言以蔽之就是先进先出。比如入队列的顺序是1,2,3,4,那么出队列的顺序也是1,2,3,4 队列的实现 软件——GO语言实现 除了使用链...

4057
来自专栏微服务生态

淘宝Tedis组件究竟是个啥(一)

淘宝的Tedis组件究竟是个啥呢?可能有一些朋友没有听过这个名字,有一些朋友会经常使用,那么今天我就来和大家深入分析一下,它的使用和原理。

1092
来自专栏MasiMaro 的技术博文

Vista 及后续版本的新线程池

在上一篇的博文中,说了下老版本的线程池,在Vista之后,微软重新设计了一套线程池机制,并引入一组新的线程池API,新版线程池相对于老版本的来说,它的可控性更高...

1573
来自专栏用户2442861的专栏

web.xml配置详解

1、web.xml学名叫部署描述符文件,是在Servlet规范中定义的,是web应用的配置文件。

3691

扫码关注云+社区

领取腾讯云代金券