当前位置:

首页 > 编程开发 > Java多线程断点复制的方法是什么

Java多线程断点复制的方法是什么

细节介绍我这里是使用一个Timer类(java.util.Timer)来实现断点功能的,就是使用这个类,每隔一段时间进行一次记录,记录的内容是每个线程复制的进度。Timer类的介绍:Afacilityforthreadstoscheduletasksforfutureexecutioninabackgroundthread.Tasksmaybescheduledforone-timeexecution,orforrepeatedexecutionatregularintervals.线程在后台线程中调度任

本文目录

    细节介绍

    我这里是使用一个Timer类(java.util.Timer)来实现断点功能的,就是使用这个类,每隔一段时间进行一次记录,记录的内容是每个线程复制的进度。

    Timer 类的介绍:

    A facility for threads to schedule tasks for future execution in a background thread. Tasks may be scheduled for one-time execution, or for repeated execution at regular intervals. 线程在后台线程中调度任务以供将来执行的工具。任务可以安排为一次性执行,也可以安排为定期重复执行。

    根据 API 中的介绍可以看出,这个 Timer 类可以只执行一次任务,也可以周期性地执行任务。(注意这个类是 java.util.Timer 类,不是 javax 包下面的类。)

    这个类的有很多和时间相关的方法,这里就不介绍了,感兴趣的可以去了解,这里只介绍我们需要使用的一个方法。

    public void schedule(TimerTask task, long delay, long period)

    Schedules the specified task for repeated fixed-delay execution beginning after the specified delay. Subsequent executions take place at approximately regular intervals separated by the specified period. 为指定的任务安排在指定延迟之后开始的重复固定延迟执行。随后的执行发生在按规定时间间隔的大致间隔。

    使用这个方法,按照一个固定的时间间隔记录各个线程的复制进度信息即可。

    代码部分

    定时任务类

    package dragon.local;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    import java.util.List;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class RecordTask extends TimerTask {
    	public static final String filename = "breakPointRecord.txt";
    	private Timer timer;
    	private List copyThreads;
    	private String outputPath;
    	
    	public RecordTask(Timer timer, List copyThreads, String outputPath) {
    		this.timer = timer;
    		this.copyThreads = copyThreads;
    		this.outputPath = outputPath;
    	}
    	
    	@Override
    	public void run() {
    		try {
    			this.breakPointRecord();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	public void breakPointRecord() throws FileNotFoundException, IOException {
    		int aliveThreadNum = 0;  //存活线程数目
    		//不使用追加方式,这里只需要最新的记录即可。
    		File recordFile = new File(outputPath, filename);
    		try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(recordFile))){
    			//每次记录一个线程的下载位置,但是取出来又需要进行转换,太麻烦了。
    			//我们直接使用序列化来进行操作,哈哈!
    			long[] curlen = new long[4];
    			int index = 0;
    			for (FileCopyThread copyThread : copyThreads) {
    				if (copyThread.isAlive()) {
    					aliveThreadNum++;
    				}
    				curlen[index++] = copyThread.getCurlen();
    				System.out.println(index+" curlen: "+copyThread.getCurlen());
    			}
    			//创建 Record 对象,并序列化。
    			oos.writeObject(new Record(curlen));
    		}
    		//当所有的线程都死亡时,关闭计时器,删除记录文件。(所有线程死亡的话,就是文件已经复制完成了!)
    		if (aliveThreadNum == 0) {
    			timer.cancel();
    			recordFile.delete();
    		}
    		System.out.println("线程数量: "+aliveThreadNum);
    	}
    }

    说明:

    if (aliveThreadNum == 0) {
    	timer.cancel();
    	recordFile.delete();
    }

    如果线程都已经结束了,就表示程序已经正常执行结束了。这个时候就删除记录文件。这里这个记录文件是一个标志(flag),如果存在记录文件就表示程序没有正常结束,再次启动时,会进行断点复制。

    注意:这里没有考虑复制过程中的 IO 异常,如果线程抛出 IO 异常,那么线程的状态也是结束了。但是考虑,本地文件复制出现 IO 异常的情况还是比较少的,就没有考虑,如果是网络下载的话,这个程序的功能可能就需要进行改进了。

    记录信息类

    每次需要依次写入各个线程的信息,但是读取出来还需要进行转换,还是感觉过于麻烦了,这里直接利用Java的序列化机制了。 有时候,直接操作对象是很方便的。 注意: 数组的下标表示的就是每个线程的位置。

    package dragon.local;
    
    import java.io.Serializable;
    
    public class Record implements Serializable{
    	/**
    	 * 序列化 id
    	 */
    	private static final long serialVersionUID = 1L;
    	private long[] curlen;
    	
    	public Record(long[] curlen) {
    		this.curlen = curlen;
    	} 
    	
    	public long[] getCurlen() {
    		return this.curlen;
    	}
    }

    复制线程类

    package dragon.local;
    
    import java.io.BufferedInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    
    public class FileCopyThread extends Thread {
    	private int index;
    	private long position;
    	private long size;
    	private File targetFile;
    	private File outputFile;
    	private long curlen;      //当前下载的长度
    	
    	public FileCopyThread(int index, long position, long size, File targetFile, File outputFile) {
    		this.index = index;
    		this.position = position;
    		this.size = size;
    		this.targetFile = targetFile;
    		this.outputFile = outputFile;
    		this.curlen = 0L;
    	}
    	
    	@Override
    	public void run() {
    		try (
    			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(targetFile));
    			RandomAccessFile raf = new RandomAccessFile(outputFile, "rw")){
    			bis.skip(position);  //跳过不需要读取的字节数,注意只能先后跳
    			raf.seek(position);  //跳到需要写入的位置,没有这句话,会出错,但是很难改。
    			int hasRead = 0;
    			byte[] b = new byte[1024];
    			/**
    			 * 注意,每个线程只是读取一部分数据,不能只以 -1 作为循环结束的条件
    			 * 循环退出条件应该是两个,即写入的字节数大于需要读取的字节数 或者 文件读取结束(最后一个线程读取到文件末尾)
    			 */
    			while(curlen < size && (hasRead = bis.read(b)) != -1) {
    				raf.write(b, 0, hasRead);
    				curlen += (long)hasRead;
    				//强制停止程序。
    //				if (curlen > 17_000_000) {
    //					System.exit(0);
    //				}
    			}
    
    			System.out.println(index+" "+position+" "+curlen+" "+size);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	public long getCurlen() {   //获取当前的进度,用于记录,以便必要时恢复读取进度。
    		return position+this.curlen;
    	}
    }

    这段代码是为了测试断点复制的。如果你想要进行测试,可以将 if 判断中的条件按照你要复制的文件大小进行相应的调整。如果要进行测试,可以先将这段代码的注释取消再执行程序(然后程序退出,这时候文件没有复制完成。),然后再将这段代码注释再次执行程序,文件将会复制成功。

    				//强制停止程序。
    //				if (curlen > 17_000_000) {
    //					System.exit(0);
    //				}

    复制工具类

    package dragon.local;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.RandomAccessFile;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Timer;
    
    
    
    /**
     * 设计思路:
     * 获取目标文件的大小,然后设置复制文件的大小(这样做是有好处的),
     * 然后使用将文件分为 n 分,使用 n 个线程同时进行复制(这里我将 n 取为 4)。
     * 
     * 进一步拓展:
     * 加强为断点复制功能,即程序中断以后,
     * 仍然可以继续从上次位置恢复复制,减少不必要的重复开销
     * */
    
    public class FileCopyUtil {
    	//设置一个常量,复制线程的数量
    	private static final int THREAD_NUM = 4;
    	
    	private FileCopyUtil() {}
    	
    	/**
    	 * @param targetPath 目标文件的路径
    	 * @param outputPath 复制输出文件的路径
    	 * @throws IOException 
    	 * @throws ClassNotFoundException 
    	 * */
    	public static void transferFile(String targetPath, String outputPath) throws IOException, ClassNotFoundException {
    		File targetFile = new File(targetPath);
    		File outputFilePath = new File(outputPath);
    		if (!targetFile.exists() || targetFile.isDirectory()) {   //目标文件不存在,或者是一个文件夹,则抛出异常
    			throw new FileNotFoundException("目标文件不存在:"+targetPath);
    		}
    		if (!outputFilePath.exists()) {     //如果输出文件夹不存在,将会尝试创建,创建失败,则抛出异常。
    			if(!outputFilePath.mkdir()) {
    				throw new FileNotFoundException("无法创建输出文件:"+outputPath);
    			}
    		}
    		
    		long len = targetFile.length();
    		
    		File outputFile = new File(outputFilePath, "copy"+targetFile.getName());
    		createOutputFile(outputFile, len);    //创建输出文件,设置好大小。
    		
    		//创建计时器 Timer 对象
    		Timer timer = new Timer();
    	
    		long[] position = new long[4];
    		//每一个线程需要复制文件的起点
    		long size = len / FileCopyUtil.THREAD_NUM + 1;     //保存复制线程的集合
    		List copyThreads = new ArrayList<>();
    		Record record = getRecord(outputPath);
    		
    		for (int i = 0; i < FileCopyUtil.THREAD_NUM; i++) {
    			//如果已经有了 记录文件,就从使用记录数据,否则就是新的下载。
    			position[i] = record == null ? i*size : record.getCurlen()[i];
    			FileCopyThread copyThread = new FileCopyThread(i, position[i], size, targetFile, outputFile);
    			copyThread.start();     //启动复制线程
    			copyThreads.add(copyThread);   //将复制线程添加到集合中。
    		}
    	
    		timer.schedule(new RecordTask(timer, copyThreads, outputPath), 0L, 100L);  //立即启动计时器,每隔10秒记录一次位置。
    		System.out.println("开始了!");
    	}
    	
    	//创建输出文件,设置好大小。
    	private static void createOutputFile(File file, long length) throws IOException {
    		try (   
    			RandomAccessFile raf = new RandomAccessFile(file, "rw")){
    			raf.setLength(length);
    		}
    	}
    	
    	//获取以及下载的位置
    	private static Record getRecord(String outputPath) throws FileNotFoundException, IOException, ClassNotFoundException {
    		File recordFile = new File(outputPath, RecordTask.filename);
    		if (recordFile.exists()) {
    			try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(recordFile))){
    				return (Record) ois.readObject();
    			}
    		}
    		return null;
    	}
    }

    说明: 根据复制的目录中,是否存在记录文件来判断是否启动断点复制。

    private static Record getRecord(String outputPath) throws FileNotFoundException, IOException, ClassNotFoundException {
    		File recordFile = new File(outputPath, RecordTask.filename);
    		if (recordFile.exists()) {
    			try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(recordFile))){
    				return (Record) ois.readObject();
    			}
    		}
    		return null;
    	}

    启动断点复制原来其实很简单,就是和复制一样,只不过起始复制位置变成了记录的位置了。

    //如果已经有了 记录文件,就从使用记录数据,否则就是新的下载。
    position[i] = record == null ? i*size : record.getCurlen()[i];
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。