public List readLines(String filePath) throws IOException {
List lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new FileReader(filePath, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
優點:內存友好(流式)、可指定編碼、控制力強
List lines = Files.readAllLines(Paths.get(filePath), StandardCharsets.UTF_8);
缺點:一次性讀入內存,大文件直接 OOM,生產環境大文件禁用。
// 流式處理,不載入全量內存
try (Stream stream = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8)) {
stream.filter(line -> !line.isEmpty())
.map(String::trim)
.forEach(System.out::println);
}
// 並行處理大文件(需線程安全操作)
Files.lines(path).parallel().forEach(line -> process(line));
推薦:大文件、需過濾轉換用 Files.lines;小文件簡單用 readAllLines。
public Map parseKvFile(String filePath) throws IOException {
Map map = new HashMap<>();
try (BufferedReader reader = new BufferedReader(
new FileReader(filePath, StandardCharsets.UTF_8))) {
String line;
int lineNum = 0;
while ((line = reader.readLine()) != null) {
lineNum++;
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue; // 跳過空行註釋
String[] parts = line.split("[=:]", 2); // 支援 = 或 : 分隔,只分兩段
if (parts.length == 2) {
map.put(parts[0].trim(), parts[1].trim());
} else {
log.warn("第 {} 行格式錯誤: {}", lineNum, line);
}
}
}
return map;
}
public void listFilesRecursive(File dir, List result) {
File[] files = dir.listFiles();
if (files == null) return; // 無權限或非目錄
for (File file : files) {
if (file.isDirectory()) {
listFilesRecursive(file, result);
} else {
result.add(file);
}
}
}
風險:極深目錄結構導致 StackOverflowError。
public List listFilesIterative(String rootPath) {
List result = new ArrayList<>();
Deque stack = new ArrayDeque<>();
stack.push(new File(rootPath));
while (!stack.isEmpty()) {
File current = stack.pop();
File[] files = current.listFiles();
if (files == null) continue;
for (File file : files) {
if (file.isDirectory()) {
stack.push(file); // 深度優先;改用 Queue 則廣度優先
} else {
result.add(file);
}
}
}
return result;
}
// 遍歷所有常規文件,最大深度 10
try (Stream stream = Files.walk(Paths.get(rootPath), 10,
FileVisitOption.FOLLOW_LINKS)) {
stream.filter(Files::isRegularFile)
.forEach(path -> System.out.println(path.toAbsolutePath()));
}
// 並行遍歷
Files.walk(root).parallel().filter(Files::isRegularFile).forEach(this::process);
優勢:代碼極簡、自動關閉資源、支援並行、處理循環鏈接不死循環。
| 方式 | 耗時(ms) | 內存峰值 | 備註 |
|---|---|---|---|
| 遞歸 | 39,328 | 高 | 棧幀開銷大 |
| 非遞歸 | 37,469 | 低 | 手動棧,略快 |
| Files.walk | 35,200 | 低 | 原生優化,推薦 |
| Files.walk 並行 | 28,500 | 中 | 多核加速明顯 |
結論:首選 Files.walk (並行),次選非遞歸手寫,避免純遞歸。
FOLLOW_LINKS 需配合 maxDepth 防死循環List,用 Stream 邊遍歷邊處理File.toPath() 統一用 Path APIFiles.lines/Files.walk 返回的 Stream 必須 try-with-resources 關閉,否則文件句柄洩漏public final class FileUtils {
private FileUtils() {}
/** 逐行處理大文件,自動關閉資源 */
public static void processLines(String filePath, Consumer processor) throws IOException {
try (Stream lines = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8)) {
lines.forEach(processor);
}
}
/** 並行遍歷目錄處理文件 */
public static void walkFilesParallel(String rootPath, Consumer fileProcessor) throws IOException {
try (Stream stream = Files.walk(Paths.get(rootPath),
FileVisitOption.FOLLOW_LINKS)) {
stream.parallel()
.filter(Files::isRegularFile)
.forEach(fileProcessor);
}
}
}