獲取目錄下所有文件(含子目錄)是常見業務需求。本文提供基於 java.io.File 的遞歸實現、Java 7+ Files.walkFileTree 現代化方案、Java 8+ Stream API 一行代碼風格,並對比三種方案的性能與適用場景。
import java.io.File;
public class FileTraversal {
public static void main(String[] args) {
String path = "G:/lxz/20130611";
listAllFiles(new File(path));
}
public static void listAllFiles(File dir) {
if (!dir.exists()) {
System.out.println(path + " 不存在");
return;
}
if (!dir.isDirectory()) {
System.out.println("非目錄路徑");
return;
}
File[] files = dir.listFiles();
if (files == null) return; // 無權限或 I/O 錯誤
for (File file : files) {
if (file.isDirectory()) {
System.out.println(file.getName() + " [目錄]");
listAllFiles(file); // 遞歸進入子目錄
} else {
System.out.println(file.getName());
}
}
}
}
file.isDirectory() 區分目錄/文件file.listFiles() 返回 null 需防禦(無權限、非目錄、I/O 錯誤)import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class ModernFileTraversal {
public static void main(String[] args) throws IOException {
Path start = Paths.get("G:/lxz/20130611");
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println(file.getFileName());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
System.out.println(dir.getFileName() + " [目錄]");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
System.err.println("訪問失敗: " + file + " - " + exc.getMessage());
return FileVisitResult.CONTINUE; // 或 TERMINATE
}
});
}
}
import java.io.IOException;
import java.nio.file.*;
import java.util.stream.Stream;
public class StreamFileTraversal {
public static void main(String[] args) throws IOException {
Path start = Paths.get("G:/lxz/20130611");
// 遞歸遍歷所有文件(含目錄)
try (Stream<Path> stream = Files.walk(start)) {
stream.forEach(path -> {
if (Files.isDirectory(path))
System.out.println(path.getFileName() + " [目錄]");
else
System.out.println(path.getFileName());
});
}
// 僅文件,過濾目錄
try (Stream<Path> stream = Files.walk(start)) {
stream.filter(Files::isRegularFile)
.map(Path::getFileName)
.forEach(System.out::println);
}
}
}
Files.walk 返回 Stream<Path> 必須 try-with-resources 關閉(實現 AutoCloseable)FileVisitOption.FOLLOW_LINKS 控制| 維度 | 遞歸 File | walkFileTree | Files.walk Stream |
|---|---|---|---|
| JDK 版本 | 1.2+ | 7+ | 8+ |
| 代碼複雜度 | 低 | 中 | 極低 |
| 異常處理 | 手動 | 訪問者回調 | Stream 鏈式 |
| 符號鏈接安全 | 無保護 | 內建循環檢測 | 可選跟隨 |
| 屬性訪問 | 二次 I/O | 預讀取 BasicFileAttributes | 需額外調用 |
| 並行處理 | 困難 | 困難 | 支援 parallelStream() |
| 記憶體模型 | 全量遞歸棧 | 流式 | 流式 |
File.listFiles()Files.walkFileTree + SimpleFileVisitorFiles.walk + Stream APIFind 命令或專用索引服務// 按擴展名過濾
stream.filter(p -> p.toString().endsWith(".java"))
// 按文件大小過濾 (>1MB)
stream.filter(p -> {
try { return Files.size(p) > 1024 * 1024; }
catch (IOException e) { return false; }
})
// 按修改時間過濾 (最近 7 天)
stream.filter(p -> {
try {
FileTime mtime = Files.getLastModifiedTime(p);
return mtime.toMillis() > System.currentTimeMillis() - 7L*24*3600*1000;
} catch (IOException e) { return false; }
})
Java 文件遍歷已從手工遞歸進化為 NIO.2 標準化訪問者模式與 Stream API 函數式風格。根據項目 JDK 版本、業務複雜度、併發需求選擇合適方案,並善用 BasicFileAttributes 預讀取優化 I/O 性能,是文件系統操作的專業素養。