知识模块
☕ Java 知识模块
五、Java IO/NIO
NIO.2 Path/Files

NIO.2 文件操作(Path/Files)

面试提问

"Java NIO.2 的 Path 和 Files 类有什么用?与传统 IO 有什么区别?"


NIO.2 简介

Java 7 引入 NIO.2(JSR 203),提供了更好的文件系统 API:

作用
Path文件路径抽象
Files文件操作工具类
FileSystems文件系统工厂
WatchService文件监控服务

Path 接口

创建 Path

// 方式一:Paths 工具类
Path path1 = Paths.get("C:/temp/test.txt");
Path path2 = Paths.get("C:", "temp", "test.txt");
 
// 方式二:FileSystems
Path path3 = FileSystems.getDefault().getPath("C:/temp/test.txt");
 
// 方式三:File 转 Path
File file = new File("C:/temp/test.txt");
Path path4 = file.toPath();
 
// 方式四:Path 转 File
File file2 = path4.toFile();

Path 常用方法

Path path = Paths.get("C:/temp/subdir/test.txt");
 
path.getFileName();      // test.txt
path.getParent();        // C:/temp/subdir
path.getRoot();          // C:/
path.getNameCount();     // 3 (temp, subdir, test.txt)
path.getName(0);         // temp
path.subpath(0, 2);      // temp/subdir
 
// 路径操作
path.resolve("other.txt");     // C:/temp/subdir/test.txt/other.txt
path.resolveSibling("other.txt"); // C:/temp/subdir/other.txt
path.relativize(Paths.get("C:/temp")); // ..\..
path.normalize();              // 去除冗余的 . 和 ..
path.toAbsolutePath();         // 转绝对路径

Path vs File

特性FilePath
引入版本JDK 1.0JDK 7
异常处理返回 boolean抛出异常
符号链接不支持支持
文件属性不支持支持
性能较差更好

Files 工具类

文件操作

Path path = Paths.get("test.txt");
 
// 创建文件
Files.createFile(path);
 
// 创建目录
Files.createDirectory(Paths.get("dir"));
Files.createDirectories(Paths.get("a/b/c"));  // 创建多级目录
 
// 删除文件/目录
Files.delete(path);        // 不存在抛异常
Files.deleteIfExists(path); // 不存在返回 false
 
// 复制文件
Files.copy(source, target);
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
 
// 移动文件
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
 
// 判断存在
Files.exists(path);
Files.notExists(path);
 
// 判断类型
Files.isDirectory(path);
Files.isRegularFile(path);
Files.isSymbolicLink(path);
 
// 获取文件大小
long size = Files.size(path);

文件读写

Path path = Paths.get("test.txt");
 
// 读取所有字节
byte[] bytes = Files.readAllBytes(path);
 
// 读取所有行
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
 
// 写入字节
Files.write(path, bytes);
 
// 写入行
List<String> lines = Arrays.asList("line1", "line2");
Files.write(path, lines, StandardCharsets.UTF_8);
 
// 追加写入
Files.write(path, lines, StandardCharsets.UTF_8, 
    StandardOpenOption.APPEND);

流式读写(大文件)

// 读取流
try (BufferedReader reader = Files.newBufferedReader(path, 
        StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 处理每行
    }
}
 
// 写入流
try (BufferedWriter writer = Files.newBufferedWriter(path, 
        StandardCharsets.UTF_8)) {
    writer.write("Hello");
    writer.newLine();
    writer.write("World");
}
 
// 输入流
try (InputStream is = Files.newInputStream(path)) {
    // 读取
}
 
// 输出流
try (OutputStream os = Files.newOutputStream(path)) {
    // 写入
}

文件属性

Path path = Paths.get("test.txt");
 
// 基本属性
long size = Files.size(path);
FileTime lastModified = Files.getLastModifiedTime(path);
FileTime created = Files.getCreationTime(path);
boolean readable = Files.isReadable(path);
boolean writable = Files.isWritable(path);
boolean executable = Files.isExecutable(path);
 
// 获取所有属性
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
attrs.size();
attrs.lastModifiedTime();
attrs.isDirectory();
attrs.isRegularFile();
 
// 设置属性
Files.setAttribute(path, ":lastModifiedTime", FileTime.fromMillis(System.currentTimeMillis()));
Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis()));

目录遍历

// 列出目录内容
try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get("dir"))) {
    for (Path entry : stream) {
        System.out.println(entry.getFileName());
    }
}
 
// 过滤文件
try (DirectoryStream<Path> stream = Files.newDirectoryStream(
        Paths.get("dir"), "*.txt")) {
    for (Path entry : stream) {
        System.out.println(entry);
    }
}
 
// 深度遍历(递归)
Files.walkFileTree(Paths.get("dir"), new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        System.out.println("文件: " + file);
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        System.out.println("目录: " + dir);
        return FileVisitResult.CONTINUE;
    }
});
 
// 使用 Stream API
Files.walk(Paths.get("dir"))
    .filter(Files::isRegularFile)
    .forEach(System.out::println);
 
Files.list(Paths.get("dir"))
    .forEach(System.out::println);

文件监控(WatchService)

Path dir = Paths.get("C:/temp");
WatchService watcher = FileSystems.getDefault().newWatchService();
 
dir.register(watcher, 
    StandardWatchEventKinds.ENTRY_CREATE,
    StandardWatchEventKinds.ENTRY_DELETE,
    StandardWatchEventKinds.ENTRY_MODIFY);
 
while (true) {
    WatchKey key = watcher.take();  // 阻塞等待事件
    
    for (WatchEvent<?> event : key.pollEvents()) {
        Path changedFile = dir.resolve((Path) event.context());
        
        if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
            System.out.println("创建: " + changedFile);
        } else if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
            System.out.println("删除: " + changedFile);
        } else if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
            System.out.println("修改: " + changedFile);
        }
    }
    
    key.reset();  // 重置以继续监听
}

实用示例

复制目录

public static void copyDirectory(Path source, Path target) throws IOException {
    Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 
                throws IOException {
            Path targetDir = target.resolve(source.relativize(dir));
            Files.createDirectories(targetDir);
            return FileVisitResult.CONTINUE;
        }
        
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
                throws IOException {
            Files.copy(file, target.resolve(source.relativize(file)),
                StandardCopyOption.REPLACE_EXISTING);
            return FileVisitResult.CONTINUE;
        }
    });
}

查找文件

// 查找所有 Java 文件
Files.walk(Paths.get("src"))
    .filter(p -> p.toString().endsWith(".java"))
    .forEach(System.out::println);
 
// 按条件查找
Path found = Files.walk(Paths.get("src"))
    .filter(p -> p.getFileName().toString().equals("Main.java"))
    .findFirst()
    .orElse(null);

面试要点总结

问题答案要点
Path vs File?Path 更现代,支持符号链接、异常处理更好
Files 常用方法?copy/move/delete/readAllLines/write
大文件怎么读?newBufferedReader/newInputStream 流式读取
如何监控文件变化?WatchService
如何遍历目录?walkFileTree/walk/list

参考资料