java简单实现在线资源多线程下载,断点续存,限制最大正在下载数

为什么要使用多线程下载,断点续存,限制最大正在下载数

多线程下载已经提高了下载的效率,但是当一些特殊情况发生的时候,我们需要对程序进行处理,这样效率会更高。比如,断电断网等造成下载中断,那么我们下一次又要重新开始下载,这样效率底下,所以我们可以考虑使用断点下载。其原理主要是把每次每个线程的下载状况(已经下载的位置)保存到文件,下次读取出来,从上一次下载的位置继续下载,这样就大大提高了下载的效率。当我们最大正在下载数超过一定数量时,我们的下载网站会认为我们在攻击网站,会把我们的请求屏蔽掉。限制线程下载数量就可以解决此问题

实现说明

多线程下载

URL url = new URL("http://mirror.bit.edu.cn/apache/tomcat/tomcat-9/v9.0.22/bin/apache-tomcat-9.0.22.zip");

从url获取下载资源的连接 这里是从apache官网下载tomcat9.0

		URLConnection connection = url.openConnection();//获取连接
		long length = connection.getContentLengthLong();//得到下载文件总长度
		int blockSize = 1024 * 1024 * 2;//分2M一个线程
		int forCount = (int) (length % blockSize == 0 ? length / blockSize : length / 	  blockSize + 1);//总共下载线程

分好下载part(部分)后,开启多线程下载

for (int i = 0; i < forCount; i++) {
			int begin = i * blockSize;//第i part的开始位置
			int end = begin + blockSize;//第i part的结束位置
			URLConnection conn = url.openConnection();
			int index = i;
			//开启多线程下载
			Thread t = new Thread() {
				public void run() {
					try {
						downlown(conn, downloadDisk, begin, end, index);
					} catch (Exception e) {
						e.printStackTrace();
					}
				};
			};
			t.start();
		}

断点续存
需要用到RandomAccessFile类,如果用FileOutputStream,文件会把已经下载的直接覆盖掉, RandomAccessFile可以通过seek方法将指针位置移动

//当此时程序突然停止,将已经下载的值存储下来
map.put("part"+i, downSize);//“part”+i为第i部分的键名,downSize为i部分线程已经下载到的字节位置,把此信息存入map中我们就可以获取突然程序中断前的下载位置记录,断点续存时从此位置开始读
//将map的内容存入txt文件
MapFileConvert.mapToFile(saveMapPath, map);//saveMapPath为存map的文件路径,map为记录中断前的下载记录
Map<String,Integer> fileToMap = MapFileConvert.fileToMap(saveMapPath);//判断当前part是否下载 若已经下载则取出已经下载的记录值

指针从0开始 配合RandomAccessFile使用 begin为每一部分开始的字节数位置

  RandomAccessFile saveFile = new RandomAccessFile( downloadDisk + "_" + i, "rw");

  int pointer = 0;//如果没有已经下载记录的位置,从起始位置开始  表示需要跳过的字节数
    		if(fileToMap != null && fileToMap.get("part" + i) != null){
    			//pointer(已经下载过的部分字节位置需要跳过)
    			pointer = fileToMap.get("part" + i) - begin;
    			//设置要跳过已经下载的进度
    			begin = fileToMap.get("part" + i);//开始下载记录的字节数位置
    		}
		
//断点续存 seek inputStream跳过前面的begin部分 saveFile将已经下载的部分跳过 指针从pointer(已经下载过的部分)开始写
		inputStream.skip(begin);
		saveFile.seek(pointer);

下载

	//每一part开始下载
while ((len = inputStream.read(buffer)) != -1 && downSize < end) {
	if (downSize + len > end) {
		len = 1024 - (downSize + len - end);
	}
	downSize += len;
	//写入文件
	saveFile.write(buffer, 0, len);//此位置不能使用fos.write(buffer, 0, len);会覆盖之前下载的内容
}

断点续存效果:我实现的是当正在下载的时候终止程序,再次启动程序的时候从上一次正在下载位置下载

限制最大正在下载数

static int downloadingCount = 0;// 定义一个正在下载的静态变量
刚开始下载

//下面代码都是在下载的方法中

downloadingCount++;//每次进入下载 正在下载数加1  做最大正在下载数统计

//当正在下载的数量大于最大正在下载数时停止下载  在此等待其他线程下载完成
	while (downloadingCount > downloadingmaxNum) {
		synchronized (Download.class) {
			Download.class.wait();
		}
	}

下载完成后

//当一个线程完成下载后  正在下载的数量减1
		synchronized (Download.class) {
			downloadingCount--;
			//通知所有等待线程  已经完成数 和正在下载数的发生变化
			Download.class.notifyAll();
		}

完整实现代码
先创建一个download.properties的配置文件,存相关配置信息
download.properties内容如下:

downloadingmaxNum=5
downloadPathPrefix=E:/downloadtest/
saveMapPath=E:/downloadtest/map.txt

默认存储位置需要换的可以改成你的目录,这里最大下载数5

准备一个map存入文件和取出的工具类 MapFileConvert
MapFileConvert.java内容如下:

package com.jt.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Map;

public class MapFileConvert {

	public  static void mapToFile(String filePath,Map map) throws Exception{
		File file = fileIsExist(filePath);
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
		oos.writeObject(map);
		oos.flush();
		oos.close();
	}
	public static Map fileToMap(String filePath){
		Map map = null;
		try {
			File file = fileIsExist(filePath);
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
			map = (Map) ois.readObject();
			ois.close();
		} catch (Exception e) {
			//e.printStackTrace();
		}
		return map;
	}
	private static File fileIsExist(String filePath) {
		File file = new File(filePath);
		if (!file.exists()) {
			System.out.println("文件不存在,现在创建");
			try {
				file.createNewFile();
			} catch (IOException e) {
				System.out.println("路径:" + filePath + "创建失败");
				e.printStackTrace();
			}
		}
		return file;
	}
}

下面是实现多线程下载的主类Download
Download.java内容如下:

package com.jt.url;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

import com.jt.utils.MapFileConvert;

public class Download {
	static int downloadedCount = 0;
	static int downloadedSize = 0;
	static int downloadedRate = 0;
	static int downloadingCount = 0;// 正在下载的个数
	static ResourceBundle bundle;//读取配置文件的类
	static String downloadPathPrefix;//下载的文件需要存入的路径前缀 默认E:/downloadtest/
	static int downloadingmaxNum;//默认最大正在下载数
	static String saveMapPath;//存储已经下载的文件的位置路径
	static Map<String, Integer> map;//存取已经下载文件位置的map
	static {
		//读取src下的download.properties文件
		bundle = ResourceBundle.getBundle("download");
	}

	public static void main(String[] args) throws Exception {
		
		downloadPathPrefix = bundle.getString("downloadPathPrefix");
		downloadingmaxNum = Integer.parseInt(bundle.getString("downloadingmaxNum"));
		saveMapPath = bundle.getString("saveMapPath");
		map = new HashMap<>();

		//从url获取下载资源的连接
		URL url = new URL("http://mirror.bit.edu.cn/apache/tomcat/tomcat-9/v9.0.22/bin/apache-tomcat-9.0.22.zip");
		String[] split = url.getPath().split("/");
		String fileName = split[split.length - 1];
		String downloadDisk = downloadPathPrefix + fileName;
		URLConnection connection = url.openConnection();
		
		long length = connection.getContentLengthLong();//文件总长度
		int blockSize = 1024 * 1024 * 2;//分2M一个线程
		int forCount = (int) (length % blockSize == 0 ? length / blockSize : length / blockSize + 1);//总共线程
		long ltime = System.currentTimeMillis();//计算下载完成的所有时间
		
		for (int i = 0; i < forCount; i++) {
			int begin = i * blockSize;//第i part的开始位置
			int end = begin + blockSize;//第i part的结束位置
			URLConnection conn = url.openConnection();
			int index = i;
			//开启多线程下载
			Thread t = new Thread() {
				public void run() {
					try {
						downlown(conn, downloadDisk, begin, end, index);
					} catch (Exception e) {
						e.printStackTrace();
					}
				};
			};
			t.start();
		}

		// 当已经下载数完成则结束等待
		while (downloadedCount < forCount) {
			synchronized (Download.class) {
				Download.class.wait();
			}
		}

		System.out.println("总下载共用时间:" + (System.currentTimeMillis() - ltime));
		// 合并文件
		FileOutputStream fos = new FileOutputStream(downloadDisk);
		for (int i = 0; i < forCount; i++) {
			FileInputStream fis = new FileInputStream(downloadDisk + "_" + i);
			byte[] buffer = new byte[1024];
			int len;
			while ((len = fis.read(buffer)) != -1) {
				fos.write(buffer, 0, len);
			}
			fis.close();
		}
		fos.close();
	}

	private static void downlown(URLConnection conn, String downloadDisk, int begin, int end, int i) throws Exception {
		InputStream inputStream = conn.getInputStream();
		//如果用FileOutputStream,文件会把已经下载的直接覆盖掉,   RandomAccessFile可以通过seek方法将指针位置移动
		RandomAccessFile saveFile = new RandomAccessFile( downloadDisk + "_" + i, "rw");
		
		//FileOutputStream fos = new FileOutputStream(downloadDisk + "_" + i);
		int contentLength = conn.getContentLength();
		
		//判断当前part是否下载  若已经下载则取出已经下载的记录值
		Map<String,Integer> fileToMap = MapFileConvert.fileToMap(saveMapPath);
		//指针从0开始配合RandomAccessFile使用
		int pointer = 0;
		if(fileToMap != null && fileToMap.get("part" + i) != null){
			pointer = fileToMap.get("part" + i) - begin;
			//将进度条定位到之前下载的进度
			Download.downloadedSize += pointer;
			//设置要跳过已经下载的进度
			begin = fileToMap.get("part" + i);
		}
		
		//断点续存 seek inputStream跳过前面的begin部分 saveFile将已经下载的部分跳过 指针从pointer(已经下载过的部分)开始写
		inputStream.skip(begin);
		saveFile.seek(pointer);
		
		int downSize = begin;
		byte[] buffer = new byte[1024];
		int len;
		long time = System.currentTimeMillis();
		System.out.println("第" + i + "块开始");
		//每次进入下载 正在下载数加1  做最大正在下载数统计
		Download.downloadingCount++;
		
		//当正在下载的数量大于最大正在下载数时停止下载
		while (Download.downloadingCount > Download.downloadingmaxNum) {
			synchronized (Download.class) {
				Download.class.wait();
			}
		}
		
		//每一part开始下载
		while ((len = inputStream.read(buffer)) != -1 && downSize < end) {
			if (downSize + len > end) {
				len = 1024 - (downSize + len - end);
			}
			downSize += len;
			
			//计算进度 将进度显示在控制台 当前进度>=之前进度%1时 进度变化
			synchronized (Download.class) {
				Download.downloadedSize += len;
				int rate = Download.downloadedSize * 100 / contentLength;
				if (rate > Download.downloadedRate) {
					System.out.println("当前文件下载进度:" + rate + "% 下载数:" + downloadingCount + " 下载位置" + i + ":" + downSize);
					Download.downloadedRate = rate;
				}
			}
			//写入文件
			//fos.write(buffer, 0, len);
			saveFile.write(buffer, 0, len);
			//当此时程序突然停止,将已经下载的值存储下来  
			map.put("part"+i, downSize);
			//将map的内容存入txt文件
			MapFileConvert.mapToFile(saveMapPath, map);
		}
		System.out.println("第" + i + "块所需时间:" + (System.currentTimeMillis() - time));
		
		//当一个线程完成下载后 已经完成的数量加1 正在下载的数量减1
		synchronized (Download.class) {
			Download.downloadedCount++;
			Download.downloadingCount--;
			//通知所有等待线程  已经完成数 和正在下载数的变化
			Download.class.notifyAll();
		}
		saveFile.close();
		//fos.close();
	}
}

多线程的简单实现完成了

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值