java开发断点续传,多线程下载工具

本文介绍了一个使用Java开发的断点续传和多线程下载工具。该工具包括主类DownUtil和启动类DownUtilTest,尽管在细节处理上仍有待完善,但在实际测试中,下载国外资源的速度相比普通浏览器有所提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java开发断点续传,多线程下载工具

下载主类DownUtil

import java.io.*;
import java.net.*;
import java.util.concurrent.*;
import java.net.URLConnection.*;
import java.nio.file.*;
import java.util.regex.*;
public class DownUtil
{
	private int startPosit;
	private String path;
	private String targetFile;
	private int threadNum;
	private DownThread[] threads;
	private int fileSize;
	public int fileSize0;
	public DownUtil(String path,String targetFile,int threadNum)
	{
		this.path = path;
		this.targetFile = targetFile;
		this.threadNum = threadNum;
		this.threads = new DownThread[threadNum];
	}
	public void download()
		throws Exception
	{
		var url = new URL(path);
		var conn = (HttpURLConnection)url.openConnection();
		conn.setConnectTimeout(5*1000);
		conn.setRequestMethod("GET");
		conn.setRequestProperty("Accept-Ranges","bytes");
		conn.setRequestProperty("Content-Length","7877");
		conn.setRequestProperty("Content-Type","mage/png");
		fileSize = conn.getContentLength();
		fileSize0 = conn.getContentLength();
		conn.disconnect();
		var currentPartSize = fileSize / threadNum + 1;
		var file = new RandomAccessFile(targetFile,"rw");
		file.setLength(fileSize);
		file.close();
		for (var i = 0; i < threadNum; i++)
		{
			Thread.sleep(500);
			int startPosit = i * currentPartSize;
			var currentPart = new RandomAccessFile(targetFile,"rw");
			currentPart.seek(startPosit);
			//被分配到的文件快,该文件快大小,开始位置
			threads[i] = new DownThread(currentPart,currentPartSize,startPosit,i);
			var thread0 = threads[i];
			//并发池
			var pool = Executors.newFixedThreadPool(threadNum);
			pool.submit(thread0);
			pool.shutdown();
		}
	}
	public double getCompleteRate()
	{
		int sum = 0;
		for (var i = 0; i < threadNum; i++)
		{
			sum += threads[i].length;
		}
		return sum * 1.0 /  fileSize;
	}
	private class DownThread implements Runnable
	{
		//保存指针文件
		private File file;
		//网络文件输入流
		private InputStream inputStream;
		//指针文件对应的RandomAccessFile
		private RandomAccessFile raf;
		//本次下载开始位置
		private int startPosit;
		//下载到本地的文件对应的RandomAccessFile
		private RandomAccessFile currentPart;
		//当前线程被分配到的总任务量
		private int currentPartSize;
		//当前线程已经完成的任务量(多次断点下载合计,非仅仅本次已经下载的任务量)
		private int length;
		//线程名
		private int threadName;
		private DownThread(RandomAccessFile currentPart,int currentPartSize,int startPosit,int threadName)
		{
			this.currentPartSize = currentPartSize;
			this.currentPart = currentPart;
			this.startPosit = startPosit;
			this.threadName = threadName;
		}
		private class saveLastPosit implements Runnable
		{
			//定期保存读写位置,下次要是下载同一个文件应该先检索该文件的指针位置
			@Override
			public void run()
			{
				try
				{
					//提取文件名.前面的部分
					String[] targetFileArr = targetFile.split("\\.");
					//拼接完整的保存指针文件名
					String positFileName = targetFileArr[0] + "_lastposit_" + threadName + ".txt";
					file = new File(positFileName);
					//检查是否已经有保存指针的位置的文件,如果有就把上次的指针位置记录下来
					if(file.exists())
					{
						System.out.println("该文件已存在,继续下载");
						raf = new RandomAccessFile(file,"rw");
						//把指针移动开始
						raf.seek(0);
						var buffer = new byte[1024];
						//开始读取输入流,把读取结果缓存在buffer,读到的最后一位数据的位置为hasRead
						raf.read(buffer);
						//把buffer转换成字符串,并且用正则表达式提取数字
						var rafStr = new String(buffer);
						rafStr = rafStr.replaceAll("\\D","");
						var m = Pattern.compile("\\d*").matcher(rafStr);
						System.out.println(m.find());
						//返回匹配到的有效数字字符串
						String seekPoist = m.group();
						//把取到的指针位置存到startPosit
						startPosit = Integer.valueOf(seekPoist);//1
						//把本地文件指针指到开始位置
						currentPart.seek(startPosit);
						//计算该线程已经写入的文件大小
						//本次下载的起始位置 减去 该线程负责负责下载的起始位置(不限于本次)
						length = startPosit - (currentPartSize * threadName);
					}
					//如果是第一次下载本文件,就创建临时文件用来保存指针
					else
					{
						System.out.println();
						raf = new RandomAccessFile(positFileName,"rw");
						raf.setLength(10000);
					}
					while(true)
					{
						//每一秒,记录一次指针位置,并且把该位置写入临时文件
						Thread.sleep(1000);
						raf.seek(0);
						// 该线程负责负责下载的起始位置(不限于本次) 加上 已经写进去的长度
						raf.writeBytes(String.valueOf((currentPartSize * threadName) + length));
					}
				}
				catch(Exception ex)
				{
					ex.printStackTrace();
				}
			}
		}
		@Override
		public void run()
		{
			//正式下载之前先启动saveLastPosit
			Thread t = new Thread(new saveLastPosit());
			//设为Daemon线程
			t.setDaemon(true);
			t.start();
			try
			{
				//该sleep主要是保证saveLastPosit必要操作已经完成
				Thread.sleep(100);
				var url = new URL(path);
				var conn = (HttpURLConnection)url.openConnection();
				conn.setConnectTimeout(5*1000);
				conn.setRequestMethod("GET");
				conn.setRequestProperty("Accept-Ranges","bytes");
				conn.setRequestProperty("Content-Length","7877");
				conn.setRequestProperty("Content-Type","mage/png");
				//获取网络资源的二进制输入流
				inputStream = conn.getInputStream();
				//把指针指向网络资源起始位置(该起始位置等于本地文件起始位置)
				inputStream.skip(this.startPosit);
				var buffer = new byte[1024];
				int hasRead = 0;
				//开始读取输入流,把读取结果缓存在buffer,读到的最后一位数据的位置为hasRead
				while ((hasRead = inputStream.read(buffer)) != -1
					//如果已经写的长度没有超出本线程任务本分
					&& length < currentPartSize)
				{
					//把读进buffer所有数据写到本地文件
					currentPart.write(buffer,0,hasRead);
					//对已经写的文件量进行累加
					length += hasRead;
				}
			}
			catch(Exception ex)
			{
				ex.printStackTrace();
			}
			finally
			{
				try
				{
					currentPart.close();
					inputStream.close();
					raf.close();
					//删除临时文件currentPartSize可能会略大于length,但是不影响下载文件的质量
					if ((currentPartSize -length) < 10)
					{
						file.delete();
					}
				}
				catch(Exception ex)
				{
					System.out.println("资源关闭错误");
					ex.printStackTrace();
				}
			}
		}
	}
}

下载主程序启动类DownUtilTest

import java.util.*;
public class DownUtilTest 
{
	public static void main(String[] args) throws Exception
	{
		var d1 = new Date().getTime()/1000;
		final var du = new DownUtil("xxx(下载资源地址)xxx","xxx(指定文件名)xxx",xx(指定线程数)xx);
		du.download();
		new Thread(() -> 
		{
			while(du.getCompleteRate() < 0.999)
			{
				System.out.printf("%s%4.2f%%","已完成 ",du.getCompleteRate()*100);
				System.out.println("");
				try
				{
					Thread.sleep(100);
				}
				catch(Exception ex){}
			}
			var d2 = new Date().getTime()/1000;
			System.out.printf("%s%d%s","下载完成!,耗时",(d2 - d1),"秒。");
		}).start();
	}
}

大致是这样,很多细节处理还有欠缺
实测,下载国外资源时比一般浏览器还是要快不少。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值