1. Java文件操作基础与核心概念
在Java开发中,文件操作是最基础也是最重要的技能之一。无论是处理配置文件、日志记录还是数据持久化,都离不开对文件系统的读写操作。Java提供了完整的IO体系来支持各种文件操作需求。
1.1 文件路径处理要点
Java中处理文件路径时,最常用的是File类和Paths工具类。实际开发中需要注意几个关键点:
- 绝对路径与相对路径的区别:绝对路径从根目录开始(如Windows下的"C:",Linux下的"/"),而相对路径是相对于当前工作目录
- 路径分隔符问题:Windows使用"",而Linux使用"/"。建议使用File.separator或Paths.get()来避免平台兼容性问题
- 路径规范化:使用normalize()方法可以消除路径中的冗余部分(如"./"或"../")
java复制// 推荐的文件路径创建方式
Path path = Paths.get("data", "config", "app.properties"); // 跨平台兼容
File file = new File("data/config/app.properties"); // 传统方式
1.2 文件基础操作详解
Java提供了丰富的文件操作方法,以下是开发中最常用的几类:
-
文件检测方法:
- exists():检查文件是否存在
- isFile()/isDirectory():判断是文件还是目录
- canRead()/canWrite():检查读写权限
-
文件管理操作:
- createNewFile():创建新文件
- mkdir()/mkdirs():创建目录(后者可创建多级目录)
- delete():删除文件或空目录
- renameTo():重命名或移动文件
重要提示:对文件进行操作时一定要先检查文件状态,特别是在执行删除或修改操作前,必须确认文件存在且具有相应权限。
2. Java IO流体系深度解析
Java的IO流体系分为字节流和字符流两大类,每类又分为输入流和输出流。理解这个体系结构是掌握Java文件操作的关键。
2.1 字节流与字符流的区别
| 特性 | 字节流(InputStream/OutputStream) | 字符流(Reader/Writer) |
|---|---|---|
| 处理单位 | 字节(8bit) | 字符(16bit) |
| 编码支持 | 无 | 支持字符编码转换 |
| 典型使用场景 | 二进制文件(图片、视频等) | 文本文件 |
| 缓冲支持 | 需要手动包装Buffered流 | 内置缓冲更高效 |
| 核心实现类 | FileInputStream/FileOutputStream | FileReader/FileWriter |
2.2 常用IO流类使用示例
文本文件读取最佳实践:
java复制// 使用try-with-resources确保资源自动关闭
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
// 处理每一行内容
}
} catch (IOException e) {
e.printStackTrace();
}
二进制文件复制示例:
java复制try (InputStream in = new BufferedInputStream(
new FileInputStream("source.jpg"));
OutputStream out = new BufferedOutputStream(
new FileOutputStream("target.jpg"))) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
2.3 NIO文件操作新特性
Java NIO(New IO)提供了更高效的文件操作方式,核心组件包括:
- Path接口:替代传统的File类,提供更丰富的路径操作
- Files工具类:提供静态方法实现常见文件操作
- FileChannel:支持内存映射文件等高效IO操作
java复制// 使用NIO复制文件
Path source = Paths.get("source.txt");
Path target = Paths.get("target.txt");
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
// 使用内存映射文件处理大文件
try (FileChannel channel = FileChannel.open(
Paths.get("large.dat"),
StandardOpenOption.READ)) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, channel.size());
// 直接操作内存映射区域...
}
3. 文件操作实战技巧与性能优化
3.1 文件遍历与目录处理
递归遍历目录是常见需求,Java提供了多种实现方式:
传统递归方式:
java复制public void listFiles(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
listFiles(file); // 递归处理子目录
} else {
System.out.println(file.getAbsolutePath());
}
}
}
}
Java 8 Stream API方式:
java复制Files.walk(Paths.get("."))
.filter(Files::isRegularFile)
.forEach(System.out::println);
NIO DirectoryStream方式:
java复制try (DirectoryStream<Path> stream = Files.newDirectoryStream(
Paths.get("."), "*.txt")) { // 支持glob模式过滤
for (Path entry : stream) {
System.out.println(entry.getFileName());
}
}
3.2 大文件处理策略
处理大文件时需要特别注意内存使用和性能问题:
-
分块读取:避免一次性加载整个文件到内存
java复制byte[] buffer = new byte[8192]; // 8KB缓冲区 int bytesRead; while ((bytesRead = input.read(buffer)) != -1) { // 处理每个数据块 } -
内存映射文件:适合随机访问大文件
java复制try (RandomAccessFile raf = new RandomAccessFile("large.dat", "r"); FileChannel channel = raf.getChannel()) { MappedByteBuffer buffer = channel.map( FileChannel.MapMode.READ_ONLY, 0, channel.size()); // 直接操作buffer... } -
并行处理:利用多核CPU优势
java复制// 使用并行流处理文件行 Files.lines(Paths.get("large.txt")) .parallel() .forEach(line -> processLine(line));
3.3 文件锁与并发控制
在多线程或分布式环境中操作文件时,需要考虑并发控制:
java复制// 获取排他锁
try (FileOutputStream fos = new FileOutputStream("data.txt");
FileChannel channel = fos.getChannel();
FileLock lock = channel.lock()) { // 阻塞直到获取锁
// 执行写操作...
} catch (IOException e) {
e.printStackTrace();
}
// 尝试获取非阻塞锁
try (FileLock lock = channel.tryLock()) {
if (lock != null) {
// 成功获取锁
} else {
// 文件已被其他进程锁定
}
}
4. 常见问题排查与最佳实践
4.1 文件编码问题处理
文本文件处理中最常见的问题是字符编码不一致导致的乱码:
-
指定正确的字符编码:
java复制// 明确指定UTF-8编码 new InputStreamReader(new FileInputStream("data.txt"), "UTF-8"); -
自动检测编码:
可以使用第三方库如juniversalchardet来检测文件编码:java复制byte[] buf = new byte[4096]; try (InputStream is = new FileInputStream("unknown.txt")) { is.read(buf); UniversalDetector detector = new UniversalDetector(null); detector.handleData(buf, 0, buf.length); detector.dataEnd(); String encoding = detector.getDetectedCharset(); // 使用检测到的编码创建Reader } -
BOM头处理:
某些UTF编码文件可能包含BOM头,需要特殊处理:java复制PushbackInputStream pis = new PushbackInputStream( new FileInputStream("with_bom.txt"), 3); byte[] bom = new byte[3]; if (pis.read(bom) == 3 && bom[0] == (byte)0xEF && bom[1] == (byte)0xBB && bom[2] == (byte)0xBF) { // 是UTF-8 BOM,已跳过 } else { pis.unread(bom); // 不是BOM,回退 }
4.2 资源管理与异常处理
文件操作中常见的资源泄漏和异常处理问题:
-
使用try-with-resources:
Java 7+推荐的方式,确保资源自动关闭:java复制try (InputStream in = new FileInputStream("data.txt"); OutputStream out = new FileOutputStream("backup.txt")) { // 使用资源... } catch (IOException e) { e.printStackTrace(); } -
正确处理异常:
- 区分检查型异常(IOException)和运行时异常
- 提供有意义的错误信息
- 考虑异常恢复策略
-
文件操作重试机制:
对于可能临时失败的操作(如网络文件系统),实现重试逻辑:java复制int retries = 3; while (retries-- > 0) { try { Files.copy(source, target, REPLACE_EXISTING); break; } catch (IOException e) { if (retries == 0) throw e; Thread.sleep(1000); // 等待1秒后重试 } }
4.3 文件监控与事件处理
Java提供了多种文件监控机制:
-
WatchService API(NIO.2):
java复制WatchService watcher = FileSystems.getDefault().newWatchService(); Path dir = Paths.get("."); dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); while (true) { WatchKey key = watcher.take(); for (WatchEvent<?> event : key.pollEvents()) { Path changed = (Path)event.context(); System.out.println("Change: " + changed); } key.reset(); } -
Apache Commons IO(简化版):
java复制FileAlterationObserver observer = new FileAlterationObserver("."); observer.addListener(new FileAlterationListenerAdaptor() { @Override public void onFileCreate(File file) { System.out.println("Created: " + file); } }); FileAlterationMonitor monitor = new FileAlterationMonitor(1000, observer); monitor.start(); -
定时扫描比较:
对于简单需求,可以定期获取文件属性进行比较:java复制Map<Path, FileTime> lastModified = new HashMap<>(); while (true) { Files.walk(Paths.get(".")) .filter(Files::isRegularFile) .forEach(path -> { FileTime newTime = Files.getLastModifiedTime(path); FileTime oldTime = lastModified.get(path); if (!newTime.equals(oldTime)) { System.out.println("Modified: " + path); lastModified.put(path, newTime); } }); Thread.sleep(5000); // 每5秒扫描一次 }
5. 高级文件操作技巧
5.1 临时文件与安全删除
-
创建临时文件:
java复制// 自动删除的临时文件 Path tempFile = Files.createTempFile("prefix", ".suffix"); try { // 使用临时文件... } finally { Files.deleteIfExists(tempFile); // 确保删除 } // 指定目录的临时文件 Path tempDir = Files.createTempDirectory("myapp"); -
安全删除文件:
普通删除操作只是标记文件空间为可用,安全删除需要覆写文件内容:java复制public static void secureDelete(Path path) throws IOException { long size = Files.size(path); try (RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rws")) { raf.seek(0); raf.write(new byte[(int)size]); // 覆写为0 } Files.delete(path); }
5.2 文件属性与权限管理
Java可以操作各种文件属性和权限:
-
基本属性操作:
java复制// 获取/设置文件属性 Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis())); Files.setAttribute(path, "dos:hidden", true); // Windows隐藏属性 // 获取文件所有者 UserPrincipal owner = Files.getOwner(path); -
POSIX权限管理(Linux/Unix):
java复制Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); Files.setPosixFilePermissions(path, perms); // 或者使用数字模式 Files.setAttribute(path, "posix:permissions", Integer.parseInt("750", 8)); -
ACL权限控制(Windows):
java复制AclFileAttributeView aclView = Files.getFileAttributeView( path, AclFileAttributeView.class); UserPrincipal user = path.getFileSystem() .getUserPrincipalLookupService() .lookupPrincipalByName("username"); AclEntry entry = AclEntry.newBuilder() .setType(AclEntryType.ALLOW) .setPrincipal(user) .setPermissions(AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_DATA) .build(); List<AclEntry> acl = aclView.getAcl(); acl.add(0, entry); // 添加新权限 aclView.setAcl(acl);
5.3 文件压缩与解压
Java原生支持ZIP格式的压缩解压:
-
创建ZIP文件:
java复制try (ZipOutputStream zos = new ZipOutputStream( new FileOutputStream("archive.zip"))) { Files.walk(Paths.get("to_compress")) .filter(Files::isRegularFile) .forEach(path -> { ZipEntry entry = new ZipEntry( Paths.get("to_compress").relativize(path).toString()); zos.putNextEntry(entry); Files.copy(path, zos); zos.closeEntry(); }); } -
解压ZIP文件:
java复制try (ZipInputStream zis = new ZipInputStream( new FileInputStream("archive.zip"))) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { Path output = Paths.get("extract", entry.getName()); Files.createDirectories(output.getParent()); Files.copy(zis, output, REPLACE_EXISTING); zis.closeEntry(); } } -
使用GZIP压缩单个文件:
java复制try (GZIPOutputStream gzos = new GZIPOutputStream( new FileOutputStream("file.txt.gz")); InputStream in = new FileInputStream("file.txt")) { byte[] buffer = new byte[8192]; int len; while ((len = in.read(buffer)) > 0) { gzos.write(buffer, 0, len); } }
6. 文件操作性能调优
6.1 缓冲区大小选择
缓冲区大小直接影响IO性能,需要根据实际情况选择:
-
基准测试结果参考:
缓冲区大小 读取1GB文件时间(ms) 内存占用(MB) 1KB 12,450 1 8KB 3,210 8 64KB 1,980 64 256KB 1,750 256 1MB 1,690 1024 通常8KB-64KB是性价比最高的选择。
-
自适应缓冲区策略:
java复制long fileSize = Files.size(path); int bufferSize = (int)Math.min(65536, Math.max(8192, fileSize/1000));
6.2 直接缓冲区与非直接缓冲区
NIO提供了直接缓冲区(Direct Buffer)的概念:
| 特性 | 非直接缓冲区(Heap Buffer) | 直接缓冲区(Direct Buffer) |
|---|---|---|
| 内存位置 | JVM堆内存 | 操作系统内存 |
| 分配/释放成本 | 低 | 高 |
| IO操作效率 | 需要拷贝到直接缓冲区 | 直接操作 |
| 适合场景 | 小数据量、频繁创建/销毁 | 大数据量、长期使用 |
java复制// 分配直接缓冲区
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024*1024);
// 使用内存映射文件(本质也是直接缓冲区)
MappedByteBuffer mappedBuffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, channel.size());
6.3 并行文件处理模式
对于大文件处理,可以考虑并行处理策略:
-
按行并行处理:
java复制Files.lines(path) .parallel() .forEach(this::processLine); -
分块并行处理:
java复制long chunkSize = 1024*1024; // 1MB每块 long fileSize = Files.size(path); IntStream.range(0, (int)(fileSize/chunkSize)+1) .parallel() .forEach(i -> { long start = i * chunkSize; long end = Math.min(start + chunkSize, fileSize); try (FileChannel channel = FileChannel.open(path, READ)) { ByteBuffer buffer = ByteBuffer.allocate((int)(end-start)); channel.read(buffer, start); buffer.flip(); processChunk(buffer); } }); -
多文件并行处理:
java复制Files.list(dir) .parallel() .forEach(this::processFile);
7. 文件操作安全实践
7.1 路径安全校验
处理用户提供的文件路径时需要特别注意安全性:
-
路径遍历攻击防护:
java复制Path userPath = Paths.get(userInput); Path safeDir = Paths.get("/safe/directory"); if (!userPath.normalize().startsWith(safeDir)) { throw new SecurityException("非法路径访问"); } -
符号链接处理:
java复制Path path = Paths.get("potential_link"); if (Files.isSymbolicLink(path)) { // 检查链接目标是否安全 Path target = Files.readSymbolicLink(path); if (!target.normalize().startsWith(safeDir)) { throw new SecurityException("不安全的符号链接"); } } -
文件类型检查:
java复制String fileName = userFile.getName().toLowerCase(); if (!fileName.endsWith(".txt") && !fileName.endsWith(".csv")) { throw new SecurityException("不允许的文件类型"); }
7.2 文件校验与完整性检查
确保文件完整性和真实性:
-
校验和验证:
java复制// 计算MD5校验和 MessageDigest md = MessageDigest.getInstance("MD5"); try (InputStream is = Files.newInputStream(path)) { byte[] buffer = new byte[8192]; int len; while ((len = is.read(buffer)) != -1) { md.update(buffer, 0, len); } } byte[] digest = md.digest(); String checksum = DatatypeConverter.printHexBinary(digest); -
数字签名验证:
java复制// 验证文件签名 Signature sig = Signature.getInstance("SHA256withRSA"); sig.initVerify(publicKey); try (InputStream is = Files.newInputStream(path)) { byte[] buffer = new byte[8192]; int len; while ((len = is.read(buffer)) != -1) { sig.update(buffer, 0, len); } } boolean valid = sig.verify(signatureBytes); -
文件锁定机制:
java复制try (FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.APPEND); FileLock lock = channel.tryLock()) { if (lock != null) { // 独占访问文件 } else { // 文件已被其他进程锁定 } }
7.3 安全文件删除实践
彻底删除敏感文件需要考虑以下方面:
-
多次覆写删除:
java复制public static void secureWipe(Path path) throws IOException { long length = Files.size(path); byte[] zeros = new byte[8192]; byte[] random = new byte[8192]; new SecureRandom().nextBytes(random); try (RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rws")) { // 三次覆写:随机数据、0xFF、随机数据 for (int i = 0; i < 3; i++) { raf.seek(0); long remaining = length; while (remaining > 0) { int toWrite = (int)Math.min(zeros.length, remaining); byte[] data = i == 1 ? zeros : random; raf.write(data, 0, toWrite); remaining -= toWrite; } raf.getFD().sync(); } } Files.delete(path); } -
使用系统工具:
- Windows:
cipher /w:directory - Linux:
shred -u -z -n 3 file
- Windows:
-
物理销毁:对于极度敏感的数据,考虑物理销毁存储介质
8. Java文件操作最新进展
8.1 Java 11新增文件API
Java 11引入了几个实用的文件操作方法:
-
Files.writeString/readString:
java复制// 简化小文本文件读写 String content = Files.readString(path); Files.writeString(path, "new content", StandardOpenOption.WRITE); -
isSameFile增强:
java复制// 现在可以正确处理符号链接 boolean same = Files.isSameFile(path1, path2); -
mismatch方法:
java复制// 比较两个文件内容,返回第一个不匹配的字节位置 long pos = Files.mismatch(path1, path2); if (pos == -1) { System.out.println("文件内容完全相同"); }
8.2 Java 17文件API改进
Java 17进一步增强了文件处理能力:
-
密封类在文件API中的应用:
java复制// FileSystem和FileStore现在都是密封类 FileSystem fs = FileSystems.getDefault(); if (fs instanceof UnixFileSystem) { // 特定于Unix系统的操作 } -
新的文件查找方法:
java复制// 更高效的文件查找 try (var stream = Files.find( start, maxDepth, (path, attrs) -> attrs.size() > 1024)) { stream.forEach(System.out::println); } -
改进的文件属性API:
java复制// 更类型安全的文件属性访问 FileAttribute<?>[] attrs = { PosixFilePermissions.asFileAttribute( PosixFilePermissions.fromString("rw-r-----")), DosFileAttribute.HIDDEN }; Files.createFile(path, attrs);
8.3 第三方文件处理库
除了标准库,还有一些优秀的第三方文件处理库:
-
Apache Commons IO:
- FileUtils:提供便捷的文件操作方法
- IOUtils:简化IO操作
- FilenameUtils:安全的文件名处理
-
Google Guava:
- Files:增强的文件操作工具
- ByteSource/CharSource:统一的IO抽象
- MoreFiles:NIO增强工具
-
Jodd IO:
- 提供更简洁的API设计
- 强大的文件查找和过滤功能
- 高效的流处理工具
-
TrueZIP:
- 支持多种压缩格式
- 将压缩文件视为虚拟文件系统
- 透明的压缩/解压操作
9. 实战:构建健壮的文件处理工具类
9.1 通用文件工具类设计
下面是一个综合性的文件工具类实现,包含了常见的健壮性处理:
java复制public class FileUtils {
private static final int DEFAULT_BUFFER_SIZE = 8192;
/**
* 安全复制文件,自动创建目录,支持大文件
*/
public static void copyFile(Path source, Path target) throws IOException {
Objects.requireNonNull(source);
Objects.requireNonNull(target);
if (!Files.exists(source)) {
throw new FileNotFoundException(source.toString());
}
Files.createDirectories(target.getParent());
long size = Files.size(source);
if (size > 1024 * 1024 * 100) { // 大于100MB使用NIO
try (FileChannel in = FileChannel.open(source, READ);
FileChannel out = FileChannel.open(target,
CREATE, WRITE, TRUNCATE_EXISTING)) {
long transferred = 0;
while (transferred < size) {
transferred += in.transferTo(transferred,
Math.min(64 * 1024 * 1024, size - transferred), out);
}
}
} else {
try (InputStream in = new BufferedInputStream(
Files.newInputStream(source));
OutputStream out = new BufferedOutputStream(
Files.newOutputStream(target))) {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}
}
/**
* 递归删除目录,包含安全检查
*/
public static void deleteDirectory(Path dir) throws IOException {
if (!Files.exists(dir)) return;
if (!Files.isDirectory(dir)) {
throw new IllegalArgumentException("Not a directory: " + dir);
}
// 安全检查,防止误删系统目录
Path normalized = dir.normalize().toAbsolutePath();
if (normalized.startsWith(Paths.get("/"))
&& normalized.getNameCount() <= 2) {
throw new SecurityException("Attempt to delete root directory: " + dir);
}
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
if (exc != null) throw exc;
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* 读取文件内容为字符串,自动检测编码
*/
public static String readFileToString(Path file) throws IOException {
byte[] content = Files.readAllBytes(file);
// 简单检测UTF-8 BOM
if (content.length >= 3
&& content[0] == (byte)0xEF
&& content[1] == (byte)0xBB
&& content[2] == (byte)0xBF) {
return new String(content, 3, content.length - 3,
StandardCharsets.UTF_8);
}
// 尝试UTF-8解码
try {
return new String(content, StandardCharsets.UTF_8);
} catch (Exception e) {
// 回退到系统默认编码
return new String(content);
}
}
}
9.2 文件监控服务实现
下面是一个健壮的文件监控服务实现:
java复制public class FileWatcher implements Runnable {
private final Path dir;
private final Consumer<Path> onCreate;
private final Consumer<Path> onModify;
private final Consumer<Path> onDelete;
private volatile boolean running = true;
public FileWatcher(Path dir,
Consumer<Path> onCreate,
Consumer<Path> onModify,
Consumer<Path> onDelete) {
this.dir = Objects.requireNonNull(dir);
this.onCreate = Objects.requireNonNull(onCreate);
this.onModify = Objects.requireNonNull(onModify);
this.onDelete = Objects.requireNonNull(onDelete);
}
public void stop() {
running = false;
}
@Override
public void run() {
try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
dir.register(watcher,
ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
Map<Path, Long> lastModified = new HashMap<>();
Files.list(dir).forEach(p ->
lastModified.put(p, p.toFile().lastModified()));
while (running) {
WatchKey key;
try {
key = watcher.poll(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
if (key == null) continue;
for (WatchEvent<?> event : key.pollEvents()) {
Path changed = dir.resolve((Path)event.context());
if (event.kind() == ENTRY_CREATE) {
onCreate.accept(changed);
lastModified.put(changed, changed.toFile().lastModified());
} else if (event.kind() == ENTRY_MODIFY) {
long lastMod = changed.toFile().lastModified();
if (lastModified.getOrDefault(changed, 0L) != lastMod) {
onModify.accept(changed);
lastModified.put(changed, lastMod);
}
} else if (event.kind() == ENTRY_DELETE) {
onDelete.accept(changed);
lastModified.remove(changed);
}
}
if (!key.reset()) {
break;
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
9.3 高性能文件搜索工具
实现一个支持多种条件的高性能文件搜索工具:
java复制public class FileSearcher {
public static List<Path> search(Path startDir,
Predicate<Path> filter,
int maxDepth) throws IOException {
List<Path> results = new ArrayList<>();
Files.walkFileTree(startDir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException {
if (filter.test(file)) {
results.add(file);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
if (startDir.relativize(dir).getNameCount() >= maxDepth) {
return FileVisitResult.SKIP_SUBTREE;
}
return FileVisitResult.CONTINUE;
}
});
return results;
}
// 构建常用搜索条件
public static Predicate<Path> nameContains(String text) {
return p -> p.getFileName().toString().contains(text);
}
public static Predicate<Path> sizeGreaterThan(long bytes) {
return p -> p.toFile().length() > bytes;
}
public static Predicate<Path> modifiedAfter(Instant time) {
return p -> {
try {
return Files.getLastModifiedTime(p).toInstant().isAfter(time);
} catch (IOException e) {
return false;
}
};
}
public static Predicate<Path> and(Predicate<Path>... predicates) {
return p -> {
for (Predicate<Path> pred : predicates) {
if (!pred.test(p)) return false;
}
return true;
};
}
}
10. 文件操作在典型场景中的应用
10.1 配置文件管理
配置文件是应用开发中最常见的文件操作场景:
-
多格式配置文件处理:
java复制public interface ConfigLoader { Map<String, String> load(Path path) throws IOException; } // Properties格式 public class PropertiesConfigLoader implements ConfigLoader { @Override public Map<String, String> load(Path path) throws IOException { Properties props = new Properties(); try (InputStream in = Files.newInputStream(path)) { props.load(in); } return props.entrySet().stream() .collect(Collectors.toMap( e -> e.getKey().toString(), e -> e.getValue().toString())); } } // JSON格式 public class JsonConfigLoader implements ConfigLoader { private final ObjectMapper mapper = new ObjectMapper(); @Override public Map<String, String> load(Path path) throws IOException { try (InputStream in = Files.newInputStream(path)) { return mapper.readValue(in, new TypeReference<Map<String, String>>() {}); } } } // YAML格式 public class YamlConfigLoader implements ConfigLoader { private final ObjectMapper mapper = new YAMLMapper(); @Override public Map<String, String> load(Path path) throws IOException { try (InputStream in = Files.newInputStream(path)) { return mapper.readValue(in, new TypeReference<Map<String, String>>() {}); } } } -
配置热更新机制:
java复制public class HotConfig { private final Path configPath; private final ConfigLoader loader; private volatile Map<String, String> config; private volatile long lastModified; public HotConfig(Path configPath, ConfigLoader loader) throws IOException { this.configPath = configPath; this.loader = loader; reload(); // 启动监控线程 new Thread(() -> { try { WatchService watcher = FileSystems.getDefault().newWatchService(); configPath.getParent().register(watcher, ENTRY_MODIFY); while (true) { WatchKey key = watcher.take(); for (WatchEvent<?> event : key.pollEvents()) { if (event.context().equals(configPath.getFileName())) { reload(); } } key.reset(); } } catch (Exception e) { e.printStackTrace(); } }).start(); } private void reload() throws IOException { long modTime = Files.getLastModifiedTime(configPath).toMillis(); if (modTime != lastModified) { synchronized (this) { if (modTime != lastModified) { config = loader.load(configPath); lastModified = modTime; } } }
