Java讀取無限量文件的思路和完整代碼
前言
你需要設(shè)計(jì)一個(gè)針對 Java 環(huán)境、無線多文件夾(海量層級/文件)、文件大小不一的高效讀取系統(tǒng),核心訴求是讀取速度快且系統(tǒng)資源占用少(內(nèi)存、CPU、IO 開銷可控)。
這個(gè)場景的核心難點(diǎn)在于:1. 海量文件夾/文件的遍歷不阻塞、不溢出;2. 大文件讀取不撐爆內(nèi)存,小文件讀取不浪費(fèi)IO;3. 避免頻繁創(chuàng)建對象、減少資源競爭。
下面給出一套分層設(shè)計(jì)的最優(yōu)解決方案,兼顧高效性和低資源占用,貼合生產(chǎn)環(huán)境落地。
一、核心設(shè)計(jì)原則(先明確方向,避免踩坑)
IO 模型:優(yōu)先選用 NIO.2(java.nio.file):相比傳統(tǒng)
java.io,NIO.2 提供了更高效的文件遍歷(Files.walk/FileVisitor)、內(nèi)存映射(MappedByteBuffer),資源開銷更低。遍歷策略:深度優(yōu)先+異步非阻塞:避免遞歸遍歷導(dǎo)致的棧溢出(無線文件夾),異步處理避免主線程阻塞,提升吞吐量。
文件讀取:分場景適配(大文件/小文件):小文件批量高效讀取,大文件分片流式讀取,不加載全文件到內(nèi)存。
資源管控:線程池+內(nèi)存限制+自動(dòng)釋放:用核心線程數(shù)可控的線程池管理任務(wù),避免CPU飆升;使用try-with-resources自動(dòng)關(guān)閉流,防止句柄泄露;大文件讀取限制分片大小,控制內(nèi)存占用。
避免冗余:跳過無效文件、批量處理:過濾隱藏文件/空文件,小文件批量收集后統(tǒng)一處理,減少IO調(diào)用次數(shù)。
二、系統(tǒng)整體架構(gòu)(分層實(shí)現(xiàn),職責(zé)清晰)
整體分為 3 層,從上到下依次是:
任務(wù)調(diào)度層:負(fù)責(zé)接收根目錄、配置參數(shù)(線程數(shù)、分片大小等)、分發(fā)遍歷/讀取任務(wù)。
文件遍歷層:負(fù)責(zé)高效遍歷海量文件夾/文件,過濾無效文件,輸出有效文件路徑列表。
文件讀取層:負(fù)責(zé)分場景讀取文件(大/小文件),輸出文件內(nèi)容/元數(shù)據(jù),控制內(nèi)存占用。
三、完整實(shí)現(xiàn)代碼(帶詳細(xì)注釋,可直接運(yùn)行)
1. 核心配置類(統(tǒng)一管控參數(shù),便于調(diào)優(yōu))
import java.nio.charset.StandardCharsets;
/**
* 文件讀取系統(tǒng)配置類(集中管控參數(shù),便于優(yōu)化資源占用)
*/
public class FileReadConfig {
// 根目錄(待遍歷的起始目錄)
private String rootDir;
// 線程池核心線程數(shù)(根據(jù)CPU核心數(shù)配置,避免資源競爭)
private int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
// 線程池最大線程數(shù)
private int maxPoolSize = Runtime.getRuntime().availableProcessors() * 4;
// 大文件閾值(超過該大小視為大文件,單位:MB,可根據(jù)內(nèi)存調(diào)整)
private long largeFileThreshold = 100;
// 大文件分片讀取大?。▎挝唬篕B,控制單次內(nèi)存占用,建議不超過1024)
private int largeFileSliceSize = 512;
// 字符編碼(默認(rèn)UTF-8)
private String charset = StandardCharsets.UTF_8.name();
// 是否過濾空文件(大小為0的文件,減少無效處理)
private boolean filterEmptyFile = true;
// 省略getter/setter(可通過lombok簡化,此處為了無依賴)
public String getRootDir() { return rootDir; }
public void setRootDir(String rootDir) { this.rootDir = rootDir; }
public int getCorePoolSize() { return corePoolSize; }
public void setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; }
public int getMaxPoolSize() { return maxPoolSize; }
public void setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = maxPoolSize; }
public long getLargeFileThreshold() { return largeFileThreshold * 1024 * 1024; } // 轉(zhuǎn)換為字節(jié)
public void setLargeFileThreshold(long largeFileThreshold) { this.largeFileThreshold = largeFileThreshold; }
public int getLargeFileSliceSize() { return largeFileSliceSize * 1024; } // 轉(zhuǎn)換為字節(jié)
public void setLargeFileSliceSize(int largeFileSliceSize) { this.largeFileSliceSize = largeFileSliceSize; }
public String getCharset() { return charset; }
public void setCharset(String charset) { this.charset = charset; }
public boolean isFilterEmptyFile() { return filterEmptyFile; }
public void setFilterEmptyFile(boolean filterEmptyFile) { this.filterEmptyFile = filterEmptyFile; }
}2. 文件遍歷層(高效遍歷海量文件夾,避免棧溢出)
使用 NIO.2 的 Files.walkFileTree(基于 FileVisitor),相比遞歸 File.listFiles() 更安全(無棧溢出)、更高效(底層優(yōu)化IO),支持過濾無效文件。
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 文件遍歷層:高效遍歷海量文件夾/文件,輸出有效文件路徑(阻塞隊(duì)列緩沖,避免內(nèi)存溢出)
*/
public class FileTraverser {
// 阻塞隊(duì)列:緩沖遍歷到的有效文件路徑,平衡遍歷與讀取速度,控制內(nèi)存占用
private final BlockingQueue<Path> fileQueue = new LinkedBlockingQueue<>(1000); // 隊(duì)列容量可配置,防止積壓
private final FileReadConfig config;
// 標(biāo)記:是否遍歷完成
private volatile boolean traverseCompleted = false;
public FileTraverser(FileReadConfig config) {
this.config = config;
}
/**
* 啟動(dòng)文件遍歷(異步執(zhí)行,不阻塞主線程)
*/
public void startTraverse() {
new Thread(() -> {
try {
Path rootPath = Paths.get(config.getRootDir());
// 校驗(yàn)根目錄是否存在
if (!Files.exists(rootPath) || !Files.isDirectory(rootPath)) {
throw new IllegalArgumentException("根目錄不存在或不是文件夾:" + config.getRootDir());
}
// NIO.2 高效遍歷文件樹(深度優(yōu)先,支持無線文件夾)
Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
/**
* 訪問文件時(shí)觸發(fā)(核心邏輯:過濾無效文件,加入阻塞隊(duì)列)
*/
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// 1. 過濾空文件
if (config.isFilterEmptyFile() && attrs.size() == 0) {
return FileVisitResult.CONTINUE;
}
// 2. 過濾隱藏文件(可選,可配置)
if (file.getFileName().toString().startsWith(".")) {
return FileVisitResult.CONTINUE;
}
// 3. 將有效文件加入阻塞隊(duì)列(隊(duì)列滿時(shí)會(huì)阻塞,避免內(nèi)存積壓)
fileQueue.put(file);
return FileVisitResult.CONTINUE;
}
/**
* 訪問文件夾失敗時(shí)觸發(fā)(避免遍歷中斷)
*/
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
System.err.println("訪問文件失敗,跳過:" + file + ",異常:" + exc.getMessage());
return FileVisitResult.CONTINUE;
}
});
} catch (Exception e) {
System.err.println("文件遍歷異常:" + e.getMessage());
} finally {
// 遍歷完成,標(biāo)記狀態(tài)
traverseCompleted = true;
System.out.println("文件遍歷完成,共發(fā)現(xiàn)有效文件:" + (fileQueue.size() + " 個(gè)(隊(duì)列剩余)"));
}
}, "File-Traverse-Thread").start();
}
/**
* 獲取下一個(gè)待讀取的文件路徑(從阻塞隊(duì)列中取,線程安全)
*/
public Path getNextFile() throws InterruptedException {
// 隊(duì)列不為空,直接??;隊(duì)列為空但遍歷未完成,阻塞等待;遍歷完成且隊(duì)列為空,返回null
while (!traverseCompleted || !fileQueue.isEmpty()) {
Path file = fileQueue.poll();
if (file != null) {
return file;
}
// 短暫休眠,減少CPU空轉(zhuǎn)
Thread.sleep(10);
}
return null;
}
}3. 文件讀取層(分場景讀取,控制內(nèi)存占用)
小文件:使用
Files.readAllBytes()(NIO.2 優(yōu)化,批量讀取,效率高),但僅適用于小文件(不超過大文件閾值)。大文件:使用
FileChannel+ByteBuffer分片流式讀取,不加載全文件到內(nèi)存,每次讀取固定分片,控制內(nèi)存占用;可選內(nèi)存映射(MappedByteBuffer),適合超大文件(GB級),但注意資源釋放。
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.charset.Charset;
/**
* 文件讀取層:分場景(大/小文件)讀取,控制內(nèi)存占用,提升讀取速度
*/
public class FileReader {
private final FileReadConfig config;
public FileReader(FileReadConfig config) {
this.config = config;
}
/**
* 統(tǒng)一讀取入口:自動(dòng)判斷文件大小,選擇對應(yīng)讀取策略
*/
public void readFile(Path file) {
try {
long fileSize = Files.size(file);
System.out.println("開始讀取文件:" + file + ",文件大?。? + formatFileSize(fileSize));
if (fileSize <= config.getLargeFileThreshold()) {
// 小文件:批量讀?。ǜ咝?,代碼簡潔)
readSmallFile(file, fileSize);
} else {
// 大文件:分片流式讀?。刂苾?nèi)存占用)
readLargeFile(file, fileSize);
}
} catch (IOException e) {
System.err.println("讀取文件失?。? + file + ",異常:" + e.getMessage());
}
}
/**
* 小文件讀?。篘IO.2 Files.readAllBytes(底層優(yōu)化,比傳統(tǒng)流更快)
*/
private void readSmallFile(Path file, long fileSize) throws IOException {
// 1. 讀取全部字節(jié)(小文件,內(nèi)存占用可控)
byte[] contentBytes = Files.readAllBytes(file);
// 2. 轉(zhuǎn)換為字符串(根據(jù)配置編碼)
String content = new String(contentBytes, Charset.forName(config.getCharset()));
// 3. 處理文件內(nèi)容(此處為示例,可替換為業(yè)務(wù)邏輯)
processSmallFileContent(file, content, fileSize);
}
/**
* 大文件讀取:FileChannel + ByteBuffer 分片流式讀?。ū苊鈨?nèi)存溢出)
*/
private void readLargeFile(Path file, long fileSize) throws IOException {
// 1. 打開文件通道(try-with-resources 自動(dòng)關(guān)閉,防止句柄泄露)
try (FileInputStream fis = new FileInputStream(file.toFile());
FileChannel fileChannel = fis.getChannel()) {
// 2. 初始化ByteBuffer(分片大小,控制單次內(nèi)存占用)
ByteBuffer buffer = ByteBuffer.allocate(config.getLargeFileSliceSize());
// 3. 分片讀取文件
int bytesRead;
long totalRead = 0;
while ((bytesRead = fileChannel.read(buffer)) != -1) {
// 切換為讀模式
buffer.flip();
// 4. 處理當(dāng)前分片內(nèi)容(此處為示例,可替換為業(yè)務(wù)邏輯)
processLargeFileSlice(file, buffer, bytesRead, totalRead, fileSize);
// 5. 清空緩沖區(qū),準(zhǔn)備下一次讀取
buffer.clear();
// 6. 更新已讀取字節(jié)數(shù)
totalRead += bytesRead;
}
}
}
/**
* 處理小文件內(nèi)容(業(yè)務(wù)邏輯擴(kuò)展點(diǎn))
*/
private void processSmallFileContent(Path file, String content, long fileSize) {
// 示例:打印文件基本信息(可替換為入庫、分析等業(yè)務(wù)邏輯)
System.out.println("小文件處理完成:" + file + ",內(nèi)容長度:" + content.length() + " 字符");
}
/**
* 處理大文件分片內(nèi)容(業(yè)務(wù)邏輯擴(kuò)展點(diǎn))
*/
private void processLargeFileSlice(Path file, ByteBuffer buffer, int bytesRead, long totalRead, long fileSize) {
// 示例:打印分片信息(可替換為分片入庫、流式分析等業(yè)務(wù)邏輯)
System.out.printf("大文件分片處理:%s,當(dāng)前分片讀取:%d 字節(jié),已讀?。?d/%d 字節(jié)(%.2f%%)%n",
file, bytesRead, totalRead + bytesRead, fileSize, (totalRead + bytesRead) * 100.0 / fileSize);
}
/**
* 格式化文件大?。ū阌诖蛴∪罩荆?
*/
private String formatFileSize(long fileSize) {
if (fileSize < 1024) {
return fileSize + " B";
} else if (fileSize < 1024 * 1024) {
return String.format("%.2f KB", fileSize / 1024.0);
} else if (fileSize < 1024 * 1024 * 1024) {
return String.format("%.2f MB", fileSize / (1024.0 * 1024));
} else {
return String.format("%.2f GB", fileSize / (1024.0 * 1024 * 1024));
}
}
}4. 任務(wù)調(diào)度層(線程池管理,提升并發(fā)效率,控制資源占用)
使用 ThreadPoolExecutor 自定義線程池,避免 Executors 帶來的資源泄露風(fēng)險(xiǎn),核心線程數(shù)根據(jù)CPU核心數(shù)配置,平衡讀取速度與資源占用。
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 任務(wù)調(diào)度層:線程池管理讀取任務(wù),平衡并發(fā)效率與系統(tǒng)資源占用
*/
public class FileReadScheduler {
private final FileReadConfig config;
private final FileTraverser fileTraverser;
private final FileReader fileReader;
private ExecutorService executorService;
public FileReadScheduler(FileReadConfig config) {
this.config = config;
this.fileTraverser = new FileTraverser(config);
this.fileReader = new FileReader(config);
// 初始化線程池
initExecutorService();
}
/**
* 初始化自定義線程池(控制核心線程數(shù)、最大線程數(shù),避免資源耗盡)
*/
private void initExecutorService() {
executorService = new ThreadPoolExecutor(
config.getCorePoolSize(), // 核心線程數(shù)
config.getMaxPoolSize(), // 最大線程數(shù)
60L, // 空閑線程存活時(shí)間
TimeUnit.SECONDS, // 時(shí)間單位
new LinkedBlockingQueue<>(), // 工作隊(duì)列(無界隊(duì)列,可改為有界隊(duì)列控制積壓)
new ThreadPoolExecutor.CallerRunsPolicy() // 拒絕策略:主線程執(zhí)行,避免任務(wù)丟失
);
}
/**
* 啟動(dòng)整個(gè)文件讀取系統(tǒng)(遍歷+讀?。?
*/
public void start() {
// 1. 啟動(dòng)文件遍歷(異步)
fileTraverser.startTraverse();
// 2. 提交讀取任務(wù)到線程池(循環(huán)獲取待讀取文件,直到遍歷完成且隊(duì)列空)
new Thread(() -> {
try {
Path file;
while ((file = fileTraverser.getNextFile()) != null) {
// 提交文件讀取任務(wù)(線程池復(fù)用線程,減少資源開銷)
executorService.submit(() -> fileReader.readFile(file));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("讀取任務(wù)調(diào)度中斷:" + e.getMessage());
} finally {
// 關(guān)閉線程池(等待所有任務(wù)完成)
shutdownExecutorService();
}
}, "File-Read-Schedule-Thread").start();
}
/**
* 關(guān)閉線程池(優(yōu)雅停機(jī),避免任務(wù)丟失)
*/
private void shutdownExecutorService() {
executorService.shutdown();
try {
if (!executorService.awaitTermination(1, TimeUnit.HOURS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("所有文件讀取任務(wù)完成,線程池已關(guān)閉");
}
}5. 測試主類(快速啟動(dòng),驗(yàn)證效果)
/**
* 測試主類:配置參數(shù),啟動(dòng)文件讀取系統(tǒng)
*/
public class FileReadSystemTest {
public static void main(String[] args) {
// 1. 配置系統(tǒng)參數(shù)
FileReadConfig config = new FileReadConfig();
config.setRootDir("D:/test-files"); // 替換為你的測試根目錄
config.setCorePoolSize(4); // 核心線程數(shù)
config.setMaxPoolSize(8); // 最大線程數(shù)
config.setLargeFileThreshold(100); // 大文件閾值:100MB
config.setLargeFileSliceSize(512); // 大文件分片大小:512KB
// 2. 初始化并啟動(dòng)調(diào)度器
FileReadScheduler scheduler = new FileReadScheduler(config);
scheduler.start();
}
}四、核心優(yōu)化點(diǎn)解析(保證“快”且“資源占用少”)
NIO.2 替代傳統(tǒng) IO:
Files.walkFileTree比File.listFiles()更高效,無棧溢出風(fēng)險(xiǎn),支持海量文件夾;Files.readAllBytes()底層優(yōu)化了IO調(diào)用,比BufferedReader更快。阻塞隊(duì)列緩沖:遍歷與讀取解耦,隊(duì)列滿時(shí)阻塞遍歷線程,避免內(nèi)存積壓;隊(duì)列空時(shí)阻塞讀取線程,減少CPU空轉(zhuǎn)。
分場景讀取:小文件批量讀?。ǜ咝В笪募制魇阶x?。刂苾?nèi)存),避免大文件加載全量字節(jié)到內(nèi)存導(dǎo)致OOM。
自定義線程池:核心線程數(shù)基于CPU核心數(shù)配置,避免線程過多導(dǎo)致上下文切換頻繁;優(yōu)雅停機(jī)確保任務(wù)不丟失,資源不泄露。
自動(dòng)資源釋放:try-with-resources 自動(dòng)關(guān)閉
FileChannel/FileInputStream,防止文件句柄泄露,減少系統(tǒng)資源占用。無效文件過濾:跳過空文件、隱藏文件,減少無效IO操作,提升整體速度。
五、進(jìn)階優(yōu)化(生產(chǎn)環(huán)境可選)
大文件內(nèi)存映射(MappedByteBuffer):對于GB級超大文件,使用
FileChannel.map()實(shí)現(xiàn)內(nèi)存映射,直接操作磁盤頁緩存,比流式讀取更快,且內(nèi)存占用更低(不占用JVM堆內(nèi)存)。文件讀取緩存:對于重復(fù)讀取的小文件,使用
Caffeine做本地緩存,避免重復(fù)IO。并行遍歷:對于超海量文件夾,可將根目錄下的子文件夾分片,交給多個(gè)線程并行遍歷,提升遍歷速度。
資源監(jiān)控:加入JVM監(jiān)控(
JMX),實(shí)時(shí)監(jiān)控線程池狀態(tài)、隊(duì)列積壓、內(nèi)存占用,便于動(dòng)態(tài)調(diào)優(yōu)參數(shù)。
總結(jié)
核心方案:NIO.2 遍歷 + 分場景讀取 + 自定義線程池 + 阻塞隊(duì)列緩沖,兼顧讀取速度與低資源占用。
關(guān)鍵優(yōu)化:小文件批量讀取、大文件分片流式讀取、無效文件過濾、自動(dòng)資源釋放,避免OOM和資源泄露。
落地要點(diǎn):線程池參數(shù)基于CPU核心數(shù)配置,大文件閾值/分片大小根據(jù)服務(wù)器內(nèi)存調(diào)整,生產(chǎn)環(huán)境可疊加內(nèi)存映射和緩存優(yōu)化。
到此這篇關(guān)于Java讀取無限量文件的思路和完整代碼的文章就介紹到這了,更多相關(guān)Java讀取無限量文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot項(xiàng)目通過redis實(shí)現(xiàn)接口的冪等性
這篇文章主要為大家介紹了Springboot項(xiàng)目通過redis實(shí)現(xiàn)接口的冪等性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
java使用protobuf-maven-plugin的插件編譯proto文件詳解
這篇文章主要介紹了java使用protobuf-maven-plugin的插件編譯proto文件,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-07-07
解決mybatisPlus 中的field-strategy配置失效問題
這篇文章主要介紹了解決mybatisPlus 中的field-strategy配置失效問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Java文件操作與IO流詳解(File類+字節(jié)流+字符流)
文章主要介紹了文件、路徑和文件類型(文本文件和二進(jìn)制文件)的概念,并詳細(xì)介紹了Java中File類和FileInputStream、FileOutputStream、FileReader、FileWriter等幾個(gè)流類的操作方式,需要的朋友可以參考下2026-04-04
SpringBoot的DeferredResult案例:DeferredResult的超時(shí)處理方式
這篇文章主要介紹了SpringBoot的DeferredResult案例:DeferredResult的超時(shí)處理方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
Java list.remove( )方法注意事項(xiàng)
這篇文章主要介紹了Java list.remove( )方法注意事項(xiàng),非常簡單易懂,需要的朋友可以參考下2018-08-08
Spring?Retry?實(shí)現(xiàn)樂觀鎖重試實(shí)踐記錄
本文介紹了在秒殺商品SKU表中使用樂觀鎖和MybatisPlus配置樂觀鎖的方法,并分析了測試環(huán)境和生產(chǎn)環(huán)境的隔離級別對樂觀鎖的影響,通過簡單驗(yàn)證,展示了在可重復(fù)讀和讀已提交隔離級別下的不同行為,感興趣的朋友一起看看吧2025-03-03
java以json格式向后臺服務(wù)器接口發(fā)送請求的實(shí)例
下面小編就為大家分享一篇java以json格式向后臺服務(wù)器接口發(fā)送請求的實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01

