Saya menggunakan kelas Pemasa (java.util.Timer
) untuk melaksanakan fungsi titik putus Saya menggunakan kelas ini Rakam sekali-sekala replikasi.
Pengenalan kepada kelas Pemasa:
Kemudahan untuk urutan menjadualkan tugasan untuk pelaksanaan masa hadapan dalam urutan latar belakang mungkin dijadualkan pelaksanaan satu kali, atau untuk pelaksanaan berulang pada selang masa yang tetap. Kemudahan untuk utas menjadualkan tugas dalam utas latar untuk pelaksanaan masa hadapan. Tugas boleh dijadualkan untuk dilaksanakan sekali atau diulang secara berkala.
Mengikut pengenalan dalam API, dapat dilihat bahawa kelas Pemasa ini hanya boleh melaksanakan tugas sekali atau secara berkala. (Perhatikan bahawa kelas ini ialah kelas java.util.Timer, bukan kelas di bawah pakej javax.)
Kelas ini mempunyai banyak kaedah berkaitan masa, yang tidak akan diperkenalkan di sini kepada mereka yang berminat ketahui lebih lanjut. Di sini kami hanya memperkenalkan satu kaedah yang perlu kami gunakan.
public void schedule(TimerTask task, long delay, long period)
Gunakan kaedah ini untuk merekod maklumat kemajuan salinan setiap utas pada selang masa yang tetap. Bahagian kodKelas tugas berjadualJadualkan tugasan yang ditentukan untuk pelaksanaan kelewatan tetap berulang bermula selepas kelewatan yang ditentukan Pelaksanaan seterusnya berlaku pada selang masa tetap yang dipisahkan oleh tempoh yang ditetapkan berulangan bermula selepas kelewatan yang ditentukan. Pelaksanaan seterusnya berlaku pada selang anggaran pada selang waktu tertentu.
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<FileCopyThread> copyThreads; private String outputPath; public RecordTask(Timer timer, List<FileCopyThread> 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); } }
Penerangan:
if (aliveThreadNum == 0) { timer.cancel(); recordFile.delete(); }Jika urutan telah tamat, Ia bermakna program telah tamat seperti biasa.
Fail rekod akan dipadamkan pada masa ini. Fail rekod di sini ialah bendera Jika fail rekod wujud, ini bermakna program tidak berakhir seperti biasa Apabila ia dimulakan semula, penyalinan titik putus akan dilakukan .
Nota: Pengecualian IO semasa proses penyalinan tidak dipertimbangkan di sini Jika utas membuang pengecualian IO, status utas juga akan tamat. Walau bagaimanapun, memandangkan terdapat sedikit pengecualian IO dalam penyalinan fail tempatan, saya tidak menganggap bahawa jika ia dimuat turun dari Internet, fungsi program ini mungkin perlu diperbaiki. Kelas maklumat rekodSetiap kali, maklumat setiap thread perlu ditulis mengikut urutan, tetapi ia masih perlu ditukar selepas membacanya, yang masih terasa terlalu menyusahkan. Di sini, urutan Java digunakan secara langsung. Kadangkala mudah untuk memanipulasi objek secara langsung. Nota: Subskrip tatasusunan mewakili kedudukan setiap urutan.
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; } }Salin kelas benang
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; } }Kod ini disalin untuk menguji titik putus. Jika anda ingin menguji, anda boleh melaraskan syarat dalam penghakiman if mengikut saiz fail yang anda ingin salin. Jika anda ingin menguji, anda boleh menyahkomen kod ini sebelum melaksanakan program (kemudian program keluar, dan fail tidak disalin pada masa ini.), dan kemudian mengulas kod ini dan laksanakan atur cara sekali lagi, dan fail akan disalin dengan jayanya.
//强制停止程序。 //if (curlen > 17_000_000) { //System.exit(0); //}Kelas Alat Salin
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<FileCopyThread> 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; } }
Penerangan: Tentukan sama ada hendak memulakan penyalinan titik putus berdasarkan sama ada terdapat fail rekod dalam direktori yang disalin.
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; }Memulakan salinan titik putus ternyata sangat mudah seperti salinan, kecuali kedudukan salinan permulaan menjadi kedudukan yang direkodkan.
Atas ialah kandungan terperinci Apakah kaedah penyalinan titik putus berbilang benang Java?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!