Java利用多線程和分塊實(shí)現(xiàn)快速讀取文件
背景
在工作中經(jīng)常會有接收文件并且讀取落庫的需求,讀取方式都是串行讀取,即一行行的讀取,如果文件小還可以,但是如果文件比較大,類似于全量文件的話,這樣的讀取就會非常效率低。
本文主要介紹的是如何正確的將文件分塊,多線程的實(shí)現(xiàn)方式有多種,這里用的是CompletableFuture
方法
因?yàn)槲覀兾募锩娴拿織l數(shù)據(jù)之間沒有任何依賴關(guān)系也不存在順序要求。如何提高讀取速度,第一個想到當(dāng)然就是并行讀取文件,并行讀取的前提就是要給文件分塊,讓每個線程只讀取對應(yīng)分塊的數(shù)據(jù),先看看我們的文件格式

可以看見我們的文件格式每一行的長度不一,同時文件也無法像TCP通過指定數(shù)據(jù)體的長度來讀取數(shù)據(jù),所以如何能正確的分塊是整個方法的關(guān)鍵,如果分多或者分少了就會導(dǎo)致數(shù)據(jù)讀取錯誤的可能。

可以看見錯誤的分塊就會導(dǎo)致我們讀取的數(shù)據(jù)會被截取掉一部分,截取掉多少都是隨機(jī)的。這里我們用的方法是用填充來讓每個分塊都是正確的。具體來說就是我們在分塊的時候,判斷一下當(dāng)前的分塊位置會不會導(dǎo)致數(shù)據(jù)被截取,因?yàn)槲覀兊臄?shù)據(jù)是一行行的,所以最好的分塊位置都是分在了行尾。如果說當(dāng)前的分塊位置是在一行的中間的話,那我們就要移動這個分塊的位置到這行的行尾去
private static int THREAD_NUM = 5; long total = file.length(); long chunkSize = total < THREAD_NUM ? total : total / THREAD_NUM;
先確定要用幾個線程并行讀取,然后根據(jù)線程數(shù)和文件的大小來確定每一塊的大小,接下來就進(jìn)行判斷是否需要填充
private static long padding(long start, long chunkSize, File file) {
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")){
randomAccessFile.seek(start + chunkSize);
boolean eol = false;
//判斷當(dāng)前的位置是否需要填充,如果當(dāng)前沒有數(shù)據(jù)或者是行尾,則不需要填充
switch (randomAccessFile.read()) {
case -1:
case '\n':
eol = true;
break;
case '\r':
eol = true;
break;
default:
break;
}
//如果符合填充條件,對其進(jìn)行填充,首先是讀取一行數(shù)據(jù),然后計(jì)算出這行數(shù)據(jù)的長度,然后將這行數(shù)據(jù)的長度加上前面讀取了一字節(jié),然后將這些長度加到chunkSize上
if (!eol){
String readLine = randomAccessFile.readLine();
chunkSize += readLine.getBytes().length;
//加上前面讀取的一字節(jié)
chunkSize += 1;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return chunkSize;
}如何填充的可以看看代碼注釋,是參照了readline的實(shí)現(xiàn)思路。 經(jīng)過填充后就可以確保每塊的分塊的最后一個位置都是在行尾。將每個分塊的起始位置和分塊大小儲存起來再結(jié)合上CompletableFuture就可以多線程分塊讀取文件了
Map<Long, Long> chunkMap = new HashMap<>();
for (int i = 0; i < THREAD_NUM; i++) {
chunkSize = padding(start, chunkSize, file);
chunkMap.put(start, chunkSize);
start += chunkSize;
}
CompletableFuture.allOf(chunkMap.entrySet().stream().map(entry -> CompletableFuture.runAsync( () -> handlerReportTreeBaseData(entry.getKey(), entry.getValue()))).toArray(CompletableFuture[]::new))
.exceptionally(throwable -> {
System.out.println(throwable.getMessage());
return null;
}).join();完整實(shí)現(xiàn)
接下來給出整個的實(shí)現(xiàn)代碼,歡迎大家看看有沒有什么我沒有考慮到的,有可能的隱藏BUG和還能優(yōu)化改善的地方,歡迎討論
public class SpiltFIle {
private static int THREAD_NUM = 5;
private static void splitChunks() {
File file = new File("test.txt");
long total = file.length();
long chunkSize = total < THREAD_NUM ? total : total / THREAD_NUM;
long start = 0;
Map<Long, Long> chunkMap = new HashMap<>();
for (int i = 0; i < THREAD_NUM; i++) {
chunkSize = padding(start, chunkSize, file);
handlerReportTreeBaseData(start, chunkSize);
chunkMap.put(start, chunkSize);
start += chunkSize;
}
CompletableFuture.allOf(chunkMap.entrySet().stream().map(entry -> CompletableFuture.runAsync( () -> handlerReportTreeBaseData(entry.getKey(), entry.getValue()))).toArray(CompletableFuture[]::new))
.exceptionally(throwable -> {
System.out.println(throwable.getMessage());
return null;
}).join();
}
private static long padding(long start, long chunkSize, File file) {
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")){
randomAccessFile.seek(start + chunkSize);
boolean eol = false;
//判斷當(dāng)前的位置是否需要填充,如果當(dāng)前沒有數(shù)據(jù)或者是行尾,則不需要填充
switch (randomAccessFile.read()) {
case -1:
case '\n':
eol = true;
break;
case '\r':
eol = true;
break;
default:
break;
}
//如果符合填充條件,對其進(jìn)行填充,首先是讀取一行數(shù)據(jù),然后計(jì)算出這行數(shù)據(jù)的長度,然后將這行數(shù)據(jù)的長度加上前面讀取了一字節(jié),然后將這些長度加到chunkSize上
if (!eol){
String readLine = randomAccessFile.readLine();
chunkSize += readLine.getBytes().length;
chunkSize += 1; //加上前面讀取的一字節(jié)
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return chunkSize;
}
private static void handlerReportTreeBaseData(long start, long chunkSize) {
try (RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt", "r")) {
randomAccessFile.seek(start);
long currentCount = 0L;
String line;
while (currentCount < chunkSize && (line = randomAccessFile.readLine()) != null){
if (!line.isEmpty()){
currentCount += line.getBytes().length + System.lineSeparator().getBytes().length;
System.out.println(line);
}
}
}catch (Exception ignored){
}
}
public static void main(String[] args) throws IOException {
SpiltFIle.splitChunks();
}
}最后也是能正常的讀取完文件

以上就是Java利用多線程和分塊實(shí)現(xiàn)快速讀取文件的詳細(xì)內(nèi)容,更多關(guān)于Java讀取文件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
String實(shí)例化及static final修飾符實(shí)現(xiàn)方法解析
這篇文章主要介紹了String實(shí)例化及static final修飾符實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09
logback輸出日志屏蔽quartz的debug等級日志方式
這篇文章主要介紹了logback輸出日志屏蔽quartz的debug等級日志方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot3整合Mybatis完整版實(shí)例
本文詳細(xì)介紹了SpringBoot3整合MyBatis的完整步驟,包括添加數(shù)據(jù)庫驅(qū)動和MyBatis依賴、配置數(shù)據(jù)源和MyBatis、創(chuàng)建表和Bean類、編寫Mapper接口和XML文件、創(chuàng)建Controller類以及配置掃描包,通過這些步驟,可以實(shí)現(xiàn)SpringBoot3與MyBatis的成功整合,并進(jìn)行功能測試2025-01-01
一個Java的main方法在JVM中的執(zhí)行流程示例詳解
main方法是Java程序的入口點(diǎn),程序從這里開始執(zhí)行,這篇文章主要介紹了一個Java的main方法在JVM中執(zhí)行流程的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-09-09
詳解spring cloud構(gòu)建微服務(wù)架構(gòu)的網(wǎng)關(guān)(API GateWay)
這篇文章主要介紹了詳解spring cloud構(gòu)建微服務(wù)架構(gòu)的網(wǎng)關(guān)(API GateWay),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01
SpringBoot中GlobalExceptionHandler異常處理機(jī)制詳細(xì)說明
Spring Boot的GlobalExceptionHandler是一個全局異常處理器,用于捕獲和處理應(yīng)用程序中發(fā)生的所有異常,這篇文章主要給大家介紹了關(guān)于Java中GlobalExceptionHandler異常處理機(jī)制的相關(guān)資料,需要的朋友可以參考下2024-03-03

