Java文件斷點續(xù)傳
斷點續(xù)傳實現(xiàn)思路:將大文件均分成幾塊后,每個線程分別處理一塊數(shù)據(jù)的讀取和寫入。每次寫入都要更新記錄的日志文件,斷網(wǎng)或暫定后重新開始傳輸時,根據(jù)日志文件的信息,可以接著讀取寫入數(shù)據(jù),不用重頭開始傳輸。文章來源地址http://www.zghlxwxcb.cn/news/detail-519652.html
package com.demo;
import java.io.*;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
/**
* @descpription: 文件斷點續(xù)傳
* @date 2023/3/4
*/
public class RandomAccessFileDemo {
/**
* 指定線程數(shù)
*/
private static final Integer THREAD_NUM = 2;
public static void main(String[] args) {
String sourceStr = "~/Downloads/視頻/視頻.zip";
String targetStr = "~/Desktop/視頻123.zip";
breakpointResume(sourceStr,targetStr);
}
/**
* 斷點續(xù)傳
*
* @param sourceStr 目標文件地址
* @param targetStr 存放地址
*/
public static void breakpointResume(String sourceStr, String targetStr) {
File dataFile = new File(sourceStr);
long length = dataFile.length();
//每個線程均分文件大小,且向上取整
long part = (long) Math.ceil(length / THREAD_NUM);
//線程減法計數(shù)器
CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
Instant beginTime = Instant.now();
//記錄傳輸?shù)娜罩疚募?/span>
File logFile = new File(targetStr + ".log");
String[] splitData = null;//不是null就需要斷點續(xù)傳
BufferedReader reader = null;
try {
if (logFile.exists()) {
//存在日志文件,需要進行斷點續(xù)傳
reader = new BufferedReader(new FileReader(logFile));
String data = reader.readLine();
splitData = data.split(",");
} else {
//不存在日志文件,創(chuàng)建日志文件
logFile.createNewFile();
}
Map<Integer, Long> maps = new ConcurrentHashMap<>();
for (int i = 0; i < THREAD_NUM; i++) {
final int k = i;
System.out.println("線程正在執(zhí)行任務:" + k);
String[] finalData = splitData;
new Thread(() -> {
RandomAccessFile inFile = null;
RandomAccessFile outFile = null;
RandomAccessFile rafLog = null;
try {
inFile = new RandomAccessFile(dataFile, "r");//讀
outFile = new RandomAccessFile(targetStr, "rw");//寫
rafLog = new RandomAccessFile(logFile, "rw");//操作日志文件的流
//確定每個線程讀取文件的開始和結(jié)束的位置,有斷點續(xù)傳就從日志文件取出的位置開始讀取
inFile.seek(finalData == null ? k * part : Long.parseLong(finalData[k]));//設置每個線程讀取的啟始位置
outFile.seek(finalData == null ? k * part : Long.parseLong(finalData[k]));//設置每個線程寫入的啟始位置
byte[] bytes = new byte[1024 * 10];//每次讀取字節(jié)大小
int len = -1, allLen = 0;
while (true) {
len = inFile.read(bytes);//從磁盤讀取到緩存
if (len == -1) { //數(shù)據(jù)讀完,結(jié)束
break;
}
//如果不等于 -1,把每次讀取的字節(jié)累加
allLen = allLen + len;
//將讀取的字節(jié)數(shù)放入到map中
maps.put(k, allLen + (finalData == null ? k * part : Long.parseLong(finalData[k])));//每個線程的絕對偏移量
outFile.write(bytes, 0, len);//從緩存寫入到磁盤
//將map中的字節(jié)日志信息數(shù)據(jù)寫入磁盤
StringJoiner stringJoiner = new StringJoiner(",");
maps.forEach((key, value) -> stringJoiner.add(String.valueOf(value)));
//將日志信息寫入磁盤
rafLog.seek(0);//覆蓋之前的日志信息
rafLog.write(stringJoiner.toString().getBytes("UTF-8"));
/**
* 當前線程讀取的內(nèi)容
* allLen + (k * part)
* 或
* allLen + finalData[k] 日志文件里面的偏移量
* >=
* 下個線程的起始部分((k + 1) * part)
* 當前線程就不再讀取寫入數(shù)據(jù),結(jié)束任務
*/
if (allLen + (finalData == null ? k * part : Long.parseLong(finalData[k])) >= (k + 1) * part) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//關流
if (null != outFile && null != inFile && null != rafLog) {
try {
outFile.close();
inFile.close();
rafLog.close();
} catch (IOException e) {
e.printStackTrace();
}
}
countDownLatch.countDown();//減一
}
}).start();
}
//主線程要等到線程計數(shù)器歸零,再繼續(xù)往下執(zhí)行
countDownLatch.await();
Instant endTime = Instant.now();
System.out.println("總耗時:" + (Duration.between(beginTime, endTime).toMillis()) + "毫秒");
//刪除日志文件
logFile.delete();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
文章來源:http://www.zghlxwxcb.cn/news/detail-519652.html
到了這里,關于Java文件斷點續(xù)傳的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!