Java NIO
一、什么是NIO.2
1.1 NIO.2基本概念
NIO.2 是 Java 7 引入的新 I/O API,位于 java.nio.file包中。它是对传统 java.io.File的全面升级,提供了更强大、更灵活的文件系统操作功能。
主要改进:
1)更好的异常处理
2)更丰富的文件操作
3)支持符号链接
4)文件属性访问
5)目录遍历和监控
6)异步 I/O 操作
1.2 NIO.2 核心基础接口总结
| 组件 | 作用 |
|---|---|
Path |
替代 java.io.File,表示文件 / 目录路径(核心) |
Paths |
工具类,创建 Path 对象 |
Files |
工具类,提供所有文件操作(创建、复制、删除、读写等) |
FileSystem |
文件系统抽象,支持自定义文件系统 |
FileVisitor |
文件树遍历接口 |
WatchService |
文件系统监听服务 |
AsynchronousFileChannel |
异步文件通道 |
AsynchronousSocketChannel |
异步网络套接字通道 |
二、核心类和接口
2.1 Path接口
1. 创建Path接口对象
(最常用)
Path path1 = Paths.get("D:/test.txt");
Path path2 = Paths.get("D:", "test", "demo.txt");
2. 常用方法
Path path = Paths.get("D:/a/b/c/test.txt");
// 获取路径信息
path.getFileName(); // test.txt(文件名)
path.getParent(); // D:\a\b\c(父目录)
path.getRoot(); // D:\(根路径)
path.getNameCount(); // 4(a,b,c,test.txt)
path.getName(1); // b(索引从0开始)
// 路径解析/拼接
Path p1 = Paths.get("D:/a");
Path p2 = p1.resolve("b/test.txt"); // D:\a\b\test.txt
// 相对路径
path.relativize(Paths.get("D:/a")); // 相对路径
// 标准化路径(去除 ../ ./)
path.normalize();
// 判断是否绝对路径
path.isAbsolute();
2.2 Path接口具体实例
Path是 NIO.2 的核心,代表文件系统中的路径。
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathExample {
public static void main(String[] args) {
// 创建Path对象的几种方式
Path path1 = Paths.get("/home/user/test.txt"); // Linux/ Mac系统的绝对路径,创建 Path 对象
Path path2 = Paths.get("C:", "Users", "test", "file.txt"); // Windows 绝对路径,分段传入,自动拼接
Path path3 = Paths.get("relative/path/to/file.txt"); //相对路径 相对于 项目当前运行目录
// Path常用方法
System.out.println("文件名: " + path1.getFileName());
System.out.println("父目录: " + path1.getParent());
System.out.println("根目录: " + path1.getRoot());
System.out.println("绝对路径: " + path1.toAbsolutePath());
System.out.println("标准化路径: " + path1.normalize());
// 路径操作
Path base = Paths.get("/base");
Path resolved = base.resolve("subdir/file.txt");
Path relativized = Paths.get("/a/b/c").relativize(Paths.get("/a/b/c/d/e"));
}
}
小小解释:
1、Path = 文件/目录路径(接口)
2、Paths.get() = 静态方法,用来创建 Path 对象
3、resolve() → 拼接路径(把子路径拼到父路径后面)
从 父 → 子
如:
Path base = Paths.get("/base");
// 拼接
Path resolved = base.resolve("subdir/file.txt");
得到结果:/base/subdir/file.txt
4、relativize() → 计算相对路径(从当前路径走到目标路径怎么走)
从 某路径 → 目标路径
如下案例:
Path relativized = Paths.get("/a/b/c").relativize(Paths.get("/a/b/c/d/e"));
从 /a/b/c 出发,怎么走才能到达 /a/b/c/d/e?
结果:d/e
2.2 Paths工具类
Paths是创建 Path对象的工厂类。
// 创建Path的等价方式
Path p1 = Paths.get("/tmp/foo");
Path p2 = FileSystems.getDefault().getPath("/tmp/foo");
2.3 Files工具类
Files是 NIO.2 最重要的工具类,提供了大量静态方法。
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.List;
public class FilesExample {
public static void basicOperations() throws Exception {
Path path = Paths.get("test.txt");
// 检查文件状态
boolean exists = Files.exists(path);
boolean isDirectory = Files.isDirectory(path);
boolean isRegularFile = Files.isRegularFile(path);
boolean isReadable = Files.isReadable(path);
boolean isWritable = Files.isWritable(path);
boolean isExecutable = Files.isExecutable(path);
boolean isHidden = Files.isHidden(path);
// 比较两个文件
boolean isSameFile = Files.isSameFile(path1, path2);
}
}
三、文件操作详情
3.1 创建文件和目录
// 创建目录(多级目录用 createDirectories)
Files.createDirectory(Paths.get("D:/test"));
Files.createDirectories(Paths.get("D:/a/b/c")); // 递归创建
// 创建空文件
Files.createFile(Paths.get("D:/test.txt"));
// 创建临时文件/目录
Path tempFile = Files.createTempFile("tmp_", ".txt");
Path tempDir = Files.createTempDirectory("tmp_dir_");
// 判断是否存在
Files.exists(path);
Files.notExists(path);
// 判断是否是文件/目录
Files.isRegularFile(path);
Files.isDirectory(path);
// 获取文件大小(字节)
long size = Files.size(path);
// 获取最后修改时间
FileTime lastModified = Files.getLastModifiedTime(path);
如下实例:
import java.nio.file.*;
import java.util.HashSet;
import java.util.Set;
public class CreateOperations {
public static void main(String[] args) throws Exception {
// 1. 创建文件
Path newFile = Paths.get("newfile.txt");
Files.createFile(newFile);
// 创建文件时指定权限(UNIX系统)
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r-----"); // 把权限字符串变成权限对象
FileAttribute<Set<PosixFilePermission>> attr =
PosixFilePermissions.asFileAttribute(perms); // 包装成系统能识别的格式
Files.createFile(newFile, attr); // 创建文件同时设置权限
// 2. 创建单级目录
Path dir = Paths.get("newdir");
Files.createDirectory(dir);
// 3. 创建多级目录
Path nestedDir = Paths.get("a/b/c/d");
Files.createDirectories(nestedDir);
// 4. 创建临时文件
Path tempFile = Files.createTempFile("prefix", ".txt");
Path tempFileWithDir = Files.createTempFile(dir, "prefix", ".txt");
// 5. 创建临时目录
Path tempDir = Files.createTempDirectory("tempDir");
}
}
3.2 复制、移动和删除文件
Path src = Paths.get("src.txt");
Path dest = Paths.get("dest.txt");
// 复制文件(可选参数:覆盖、保留属性等)
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
// 移动/重命名
Files.move(src, dest, StandardCopyOption.ATOMIC_MOVE); // 原子操作
// 删除文件(文件不存在抛异常)
Files.delete(dest);
// 安全删除(不存在不抛异常)
Files.deleteIfExists(dest);
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
public class FileOperations {
public static void copyFile() throws IOException {
Path source = Paths.get("source.txt");
Path target = Paths.get("target.txt");
// 基本复制
Files.copy(source, target);
// 带选项的复制
Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING, // 覆盖已存在文件
StandardCopyOption.COPY_ATTRIBUTES, // 复制文件属性
LinkOption.NOFOLLOW_LINKS); // 不跟随符号链接
}
public static void moveFile() throws IOException {
Path source = Paths.get("oldname.txt");
Path target = Paths.get("newname.txt");
// 移动/重命名
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
// 原子移动(如果文件系统支持)
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
}
public static void deleteFile() throws IOException {
Path path = Paths.get("todelete.txt");
// 删除文件
Files.delete(path);
// 安全删除(文件不存在时不抛异常)
Files.deleteIfExists(path);
}
public static void copyDirectory() throws IOException {
Path sourceDir = Paths.get("sourceDir");
Path targetDir = Paths.get("targetDir");
// 复制整个目录
Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
Path target = targetDir.resolve(sourceDir.relativize(dir));
Files.createDirectories(target);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Path target = targetDir.resolve(sourceDir.relativize(file));
Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
}
}
关于以上最后这段java复制整个目录的标准模板,是非常重要的。
1. 核心内容
1)Files.walkFileTree
遍历目录树(递归进入所有子文件夹)
2)SimpleFileVisitor
目录遍历器,你可以监听:
进入目录前
访问文件
访问失败等
3)resolve + relativize
计算目标路径(复制到哪里)
4)new SimpleFileVisitor<Path>() 创建一个目录遍历处理器
5)preVisitDirectory(...) 进入目录前自动调用 → 用来创建目标文件夹
6)return FileVisitResult.CONTINUE 遍历继续,不要停
3.3 文件读写操作
NIO.2 提供一行代码读写文件,无需手动关闭流。
1)读取文件
// 读取所有行(返回 List<String>)
List<String> lines = Files.readAllLines(Paths.get("test.txt"), StandardCharsets.UTF_8);
// 读取所有字节
byte[] bytes = Files.readAllBytes(Paths.get("test.txt"));
// 字符流读取(BufferedReader)
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = br.readLine()) != null) {}
}
2)写入文件
// 写入字符串列表
List<String> content = Arrays.asList("第一行", "第二行");
Files.write(Paths.get("test.txt"), content, StandardCharsets.UTF_8);
// 写入字节
Files.write(Paths.get("test.txt"), "Hello NIO.2".getBytes());
// 字符流写入
try (BufferedWriter bw = Files.newBufferedWriter(path)) {
bw.write("Hello World");
}
如下实例:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.util.stream.Stream;
public class ReadWriteOperations {
public static void readFiles() throws Exception {
Path path = Paths.get("test.txt");
// 1. 读取所有字节
byte[] bytes = Files.readAllBytes(path);
// 2. 读取所有行
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
// 3. 使用BufferedReader逐行读取
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// 4. 使用Stream API读取
try (Stream<String> stream = Files.lines(path, StandardCharsets.UTF_8)) {
stream.forEach(System.out::println);
}
}
public static void writeFiles() throws Exception {
Path path = Paths.get("output.txt");
// 1. 写入字节
String content = "Hello, NIO.2!";
Files.write(path, content.getBytes(StandardCharsets.UTF_8));
// 2. 写入字符串列表
List<String> lines = List.of("Line 1", "Line 2", "Line 3");
Files.write(path, lines, StandardCharsets.UTF_8);
// 3. 使用BufferedWriter写入
try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
writer.write("Hello");
writer.newLine();
writer.write("World");
}
// 4. 追加写入
Files.write(path, List.of("New line"),
StandardCharsets.UTF_8, StandardOpenOption.APPEND);
}
}
四、文件属性操作
4.1 基本文件属性
1. 基础属性
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
attrs.creationTime(); // 创建时间
attrs.lastAccessTime(); // 最后访问时间
attrs.lastModifiedTime();// 最后修改时间
attrs.size(); // 文件大小
attrs.isDirectory(); // 是否目录
2. DOS/Windows 属性
DosFileAttributes dosAttrs = Files.readAttributes(path, DosFileAttributes.class);
dosAttrs.isReadOnly();
dosAttrs.isHidden();
3. POSIX/Linux 属性(权限、所有者)
PosixFileAttributes posixAttrs = Files.readAttributes(path, PosixFileAttributes.class);
UserPrincipal owner = posixAttrs.owner(); // 文件所有者
Set<PosixFilePermission> permissions = posixAttrs.permissions(); // 权限
// 设置 Linux 权限(rwxr-x---)
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---");
Files.setPosixFilePermissions(path, perms);
4. 文件所有者
UserPrincipalLookupService lookup = FileSystems.getDefault().getUserPrincipalLookupService();
UserPrincipal owner = lookup.lookupPrincipalByName("root");
Files.setOwner(path, owner);
如下实例:
import java.nio.file.attribute.*;
import java.nio.file.*;
import java.util.Set;
public class FileAttributes {
public static void basicAttributes() throws Exception {
Path path = Paths.get("test.txt");
// 1. BasicFileAttributes(所有文件系统都支持)
BasicFileAttributes basicAttrs = Files.readAttributes(
path, BasicFileAttributes.class);
System.out.println("创建时间: " + basicAttrs.creationTime());
System.out.println("最后访问时间: " + basicAttrs.lastAccessTime());
System.out.println("最后修改时间: " + basicAttrs.lastModifiedTime());
System.out.println("是否是目录: " + basicAttrs.isDirectory());
System.out.println("是否是普通文件: " + basicAttrs.isRegularFile());
System.out.println("是否是符号链接: " + basicAttrs.isSymbolicLink());
System.out.println("文件大小: " + basicAttrs.size());
// 2. 设置最后修改时间
FileTime time = FileTime.fromMillis(System.currentTimeMillis());
Files.setLastModifiedTime(path, time);
// 3. 获取文件所有者
FileOwnerAttributeView ownerView = Files.getFileAttributeView(
path, FileOwnerAttributeView.class);
UserPrincipal owner = ownerView.getOwner();
System.out.println("文件所有者: " + owner.getName());
}
public static void posixAttributes() throws Exception {
// POSIX文件系统属性(Linux/Unix)
Path path = Paths.get("test.txt");
PosixFileAttributes posixAttrs = Files.readAttributes(
path, PosixFileAttributes.class);
System.out.println("文件组: " + posixAttrs.group().getName());
System.out.println("权限: " + posixAttrs.permissions());
// 设置POSIX权限
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-xr-x");
Files.setPosixFilePermissions(path, perms);
}
public static void dosAttributes() throws Exception {
// DOS/Windows文件系统属性
Path path = Paths.get("test.txt");
DosFileAttributes dosAttrs = Files.readAttributes(
path, DosFileAttributes.class);
System.out.println("只读: " + dosAttrs.isReadOnly());
System.out.println("隐藏: " + dosAttrs.isHidden());
System.out.println("系统文件: " + dosAttrs.isSystem());
System.out.println("归档: " + dosAttrs.isArchive());
// 设置DOS属性
Files.setAttribute(path, "dos:readonly", true);
Files.setAttribute(path, "dos:hidden", true);
}
public static void userDefinedAttributes() throws Exception {
// 用户自定义属性
Path path = Paths.get("test.txt");
// 写入自定义属性
UserDefinedFileAttributeView userView = Files.getFileAttributeView(
path, UserDefinedFileAttributeView.class);
String attrName = "user.mimetype";
String attrValue = "text/plain";
// 写入属性
userView.write(attrName,
StandardCharsets.UTF_8.encode(attrValue));
// 读取属性
ByteBuffer buf = ByteBuffer.allocate(userView.size(attrName));
userView.read(attrName, buf);
buf.flip();
String value = StandardCharsets.UTF_8.decode(buf).toString();
}
}
小小知识总结:
1. Files.readAttributes(...) 核心方法
Files.readAttributes(路径, 要读取哪种属性.class);
含义:读取这个文件的各种属性信息
2. BasicFileAttributes.class 这个参数是什么意思?
表示我要读取最基础的文件属性(所有系统 Windows/Linux/Mac 都通用)
BasicFileAttributes 可以获取文件最核心的 5 个属性:
1)basicAttrs.size() 文件大小(字节)
2)basicAttrs.lastModifiedTime() 最后修改时间
3)basicAttrs.creationTime() 创建时间
4)basicAttrs.isRegularFile() 是否是普通文件
5)basicAttrs.isDirectory() 是否是文件夹
3. System.currentTimeMillis() 获取当前系统时间,返回的是一个毫秒数字(时间戳)。
4. FileTime.fromMillis(毫秒数)
Java NIO 规定:设置文件时间必须用 FileTime 类型,不能直接用数字,
所以这行是把毫秒转成 FileTime 对象。
5. Files.getFileAttributeView(...)
获取一个属性视图,你要什么属性,就拿什么视图。
6. FileOwnerAttributeView.class 我要的是 “文件所有者” 相关的视图
(只能看 / 改文件是谁的)
五、目录操作
5.1 目录遍历
NIO.2 支持递归遍历目录树,通过 FileVisitor 接口实现。
核心接口
-
preVisitDirectory:访问目录前
-
visitFile:访问文件
-
visitFileFailed:访问失败
-
postVisitDirectory:访问目录后
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
import java.util.stream.Stream;
public class DirectoryOperations {
public static void listDirectory() throws Exception {
Path dir = Paths.get(".");
// 1. 简单列出目录内容
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry : stream) {
System.out.println(entry.getFileName());
}
}
// 2. 使用glob模式过滤
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(dir, "*.{java,class}")) {
for (Path entry : stream) {
System.out.println(entry);
}
}
// 3. 使用过滤器
DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
public boolean accept(Path entry) throws IOException {
return Files.size(entry) > 1024; // 大于1KB的文件
}
};
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
for (Path entry : stream) {
System.out.println(entry);
}
}
}
public static void walkDirectory() throws Exception {
Path start = Paths.get(".");
// 1. 使用Files.walk(深度优先遍历)
try (Stream<Path> stream = Files.walk(start)) {
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
// 2. 限制遍历深度
try (Stream<Path> stream = Files.walk(start, 3)) { // 最多3层深度
stream.forEach(System.out::println);
}
// 3. 使用Files.find(可带过滤条件)
try (Stream<Path> stream = Files.find(start, 10,
(path, attrs) -> attrs.isRegularFile() &&
attrs.size() > 1024 * 1024)) { // 大于1MB的文件
stream.forEach(System.out::println);
}
// 4. 使用Files.list(只列出当前目录)
try (Stream<Path> stream = Files.list(start)) {
stream.forEach(System.out::println);
}
}
public static void fileVisitorExample() throws Exception {
Path start = Paths.get(".");
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
System.out.println("进入目录: " + dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException {
System.out.println("访问文件: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file,
IOException exc) throws IOException {
System.err.println("访问失败: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir,
IOException exc) throws IOException {
System.out.println("离开目录: " + dir);
return FileVisitResult.CONTINUE;
}
});
}
public static void deleteDirectory() throws Exception {
Path dir = Paths.get("toDelete");
// 删除整个目录(包括子目录和文件)
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 {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
// 或者使用更简单的方法(Java 8+)
Files.walk(dir)
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
}
}
六、文件监控
NIO.2 内置文件变化监听(监控创建、删除、修改、重命名),无需第三方框架。
1. 监听步骤
1)创建 WatchService
2)注册监听目录 + 监听事件
3)循环获取监听事件
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class FileWatcher {
public static void watchDirectory() throws Exception {
// 1. 获取WatchService实例
WatchService watchService = FileSystems.getDefault().newWatchService();
// 2. 要监控的目录
Path dir = Paths.get(".");
// 3. 注册要监控的事件类型
WatchKey key = dir.register(watchService,
ENTRY_CREATE, // 创建
ENTRY_DELETE, // 删除
ENTRY_MODIFY, // 修改
OVERFLOW); // 溢出(事件丢失)
System.out.println("开始监控目录: " + dir.toAbsolutePath());
// 4. 事件处理循环
while (true) {
// 等待事件,可设置超时
key = watchService.poll(10, TimeUnit.SECONDS);
if (key == null) {
System.out.println("等待超时,继续监控...");
continue;
}
// 处理事件
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// 处理溢出事件
if (kind == OVERFLOW) {
System.out.println("事件溢出,可能丢失了一些事件");
continue;
}
// 获取发生事件的文件名
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
// 输出事件信息
System.out.printf("事件类型: %s, 文件: %s%n", kind.name(), filename);
// 根据不同事件类型处理
if (kind == ENTRY_CREATE) {
System.out.println("文件创建: " + filename);
} else if (kind == ENTRY_DELETE) {
System.out.println("文件删除: " + filename);
} else if (kind == ENTRY_MODIFY) {
System.out.println("文件修改: " + filename);
}
}
// 重置key,继续接收事件
boolean valid = key.reset();
if (!valid) {
System.out.println("监控key失效");
break;
}
}
// 5. 关闭watchService
watchService.close();
}
public static void watchDirectoryRecursive() throws Exception {
// 如果要监控子目录,需要为每个子目录单独注册
Path startDir = Paths.get(".");
WatchService watchService = FileSystems.getDefault().newWatchService();
// 注册所有子目录
Files.walkFileTree(startDir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
dir.register(watchService,
ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
return FileVisitResult.CONTINUE;
}
});
// 处理事件(同上)
}
}
七、异步文件操作
7.1 java 异步I/O
1. NIO.2 提供真正的异步 I/O(非阻塞、回调式),分为:
异步文件 I/O:AsynchronousFileChannel
异步网络 I/O:AsynchronousSocketChannel、AsynchronousServerSocketChannel
2. 异步I/O两种模式
1)Future 模式:返回 Future,主动获取结果
2)回调模式:CompletionHandler,完成后自动回调(推荐)
7.2 异步文件读取(回调模式)
Path path = Paths.get("test.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 异步读取,回调通知
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
// 读取成功
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] bytes = new byte[attachment.limit()];
attachment.get(bytes);
System.out.println(new String(bytes));
attachment.clear();
}
// 读取失败
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
7.3 异步网络编程(服务端)
// 异步服务端
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(8888));
// 异步接受连接
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel client, Object attachment) {
// 再次监听新连接
server.accept(null, this);
// 处理客户端数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
System.out.println("客户端消息:" + new String(attachment.array(), 0, result));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {}
});
}
@Override
public void failed(Throwable exc, Object attachment) {}
});
7.4AsynchronousFileChannel 异步实例
import java.nio.channels.*;
import java.nio.ByteBuffer;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;
import java.nio.charset.StandardCharsets;
public class AsyncFileOperations {
public static void asyncReadWithFuture() throws Exception {
Path path = Paths.get("largefile.txt");
// 1. 打开异步文件通道
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
path, StandardOpenOption.READ);
// 2. 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 3. 异步读取
Future<Integer> operation = fileChannel.read(buffer, 0);
// 4. 可以做其他事情...
System.out.println("正在读取文件,可以处理其他任务...");
// 5. 等待读取完成
while (!operation.isDone()) {
// 可以检查进度或做其他事情
Thread.sleep(100);
}
// 6. 获取结果
int bytesRead = operation.get();
buffer.flip();
byte[] data = new byte[bytesRead];
buffer.get(data);
System.out.println("读取内容: " + new String(data, StandardCharsets.UTF_8));
buffer.clear();
fileChannel.close();
}
public static void asyncReadWithCompletionHandler() throws Exception {
Path path = Paths.get("largefile.txt");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 使用CompletionHandler回调
fileChannel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("读取完成,字节数: " + result);
attachment.flip();
byte[] data = new byte[attachment.limit()];
attachment.get(data);
System.out.println("内容: " + new String(data, StandardCharsets.UTF_8));
try {
fileChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("读取失败: " + exc.getMessage());
try {
fileChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 主线程可以继续执行其他任务
System.out.println("异步读取已启动,主线程继续执行...");
Thread.sleep(2000); // 等待异步操作完成
}
public static void asyncWrite() throws Exception {
Path path = Paths.get("output.txt");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
String data = "Hello, Async NIO.2!";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(data.getBytes(StandardCharsets.UTF_8));
buffer.flip();
// 异步写入
Future<Integer> writeResult = fileChannel.write(buffer, 0);
// 等待写入完成
int bytesWritten = writeResult.get();
System.out.println("写入完成,字节数: " + bytesWritten);
fileChannel.close();
}
}
八、NIO.2 vs 旧 IO / 早期 NIO
| 特性 | 旧 IO (java.io.File) | 早期 NIO (java.nio) | NIO.2 (java.nio.file) |
|---|---|---|---|
| 路径抽象 | File 类 | Path(不完善) | Path 接口(完善) |
| 文件操作 | 方法少、功能弱 | 复杂 | Files 工具类(极简) |
| 异步 I/O | 不支持 | 不支持 | 原生支持 |
| 文件树遍历 | 手动递归 | 复杂 | FileVisitor |
| 文件监听 | 无 | 无 | WatchService |
| 文件属性 | 极少 | 有限 | 完整支持(权限 / 链接) |
| 异常处理 | 混乱 | 复杂 | 统一、清晰 |
九、NIO.2 最佳实践
1)优先使用 Path + Files,完全抛弃 java.io.File
2)读写小文件直接用 Files.readAllLines()/Files.write(),代码极简
3)遍历目录用 Files.walkFileTree(),无需手动递归
4)监控文件变化用 WatchService,无需轮询
5)高并发 I/O 用异步通道(AsynchronousXXXChannel)
6)操作文件权限 / 属性直接用 NIO.2 原生 API,跨平台更稳定
总结
1)NIO.2 核心:Path(路径)+ Files(操作)+ 异步 I/O + 文件系统增强
2)核心能力:文件树遍历、文件监听、完整文件属性、异步非阻塞 I/O
3)地位:Java 官方推荐 I/O 方案,现代 Java 开发必须掌握
4)优势:API 简洁、功能强大、性能优异、跨平台兼容好
十、 NIO 练习题总结100道
模块一:Path 路径操作(15 题)
10.1 基础题(1-10)
如何通过 Paths 创建一个 Path 对象?
如何将 java.io.File 转换为 Path?
如何将 Path 转换为 java.io.File?
获取 Path 的文件名用哪个方法?
获取 Path 的父路径用哪个方法?
获取路径的根目录(如 C:\ /)用哪个方法?
判断一个路径是否是绝对路径?
路径拼接:Path.resolve() 的用法
路径标准化:Path.normalize() 解决什么问题?
获取路径中第 2 级目录名称(getName (1))
中级题(11-15)
如何获取两个路径之间的相对路径?
Path 中 subpath() 方法的使用规则
判断两个路径是否指向同一个文件(Files.isSameFile())
路径分隔符如何跨平台获取?
如何构造一个不存在的路径对象?
10.2 模块二:Files 工具类(25 题)
基础(16-30)
如何判断文件 / 目录是否存在?
创建单级目录 vs 多级目录
创建空文件
删除文件(文件不存在会抛异常)
安全删除文件(不存在不抛异常)
文件复制
文件移动 / 重命名
复制时覆盖已存在文件用哪个参数?
原子移动文件用哪个选项?
获取文件大小(字节)
读取文件所有字节
读取文件所有行(List
写入字符串列表到文件
追加内容到文件
获取文件最后修改时间
中级(31-40)
如何获取 BufferedReader/BufferedWriter
如何创建临时文件
如何创建临时目录
判断是否是普通文件
判断是否是目录
判断是否是隐藏文件
遍历一个目录下的直接子文件(list)
过滤目录中的 .txt 文件
文件大小超过 1GB 不能用哪个方法?为什么?
如何高效复制大文件?
10.3 模块三:文件属性操作(15 题)
基础(41-48)
读取文件基础属性(BasicFileAttributes)
获取文件创建时间
获取文件最后访问时间
判断是否是符号链接
Windows 系统读取 DOS 属性(只读、隐藏)
Linux 读取 POSIX 属性
获取文件所有者
修改文件最后修改时间
中级(49-55)
Linux 如何设置文件权限 rwxr-x---?
如何修改文件所有者?
如何判断文件是否是符号链接?
如何读取符号链接指向的真实路径?
如何创建符号链接?
如何获取文件的 MIME 类型?
批量读取文件属性(提高性能)
10.4 模块四:文件树遍历 FileVisitor(10 题)
文件树遍历的核心接口是什么?
SimpleFileVisitor 作用?
遍历目录时 4 个回调方法分别是什么?
如何跳过某个子目录不遍历?
如何在遍历到文件时立即终止遍历?
遍历失败如何处理?
遍历目录树并统计所有文件数量
遍历并删除所有 .log 结尾的文件
遍历并复制整个目录结构
遍历并查找大于 100MB 的大文件
10.5 模块五:文件系统监听 WatchService(10 题)
WatchService 能监听哪 3 种事件?
如何创建 WatchService?
目录注册监听的代码写法
如何获取监听事件?
如何获取发生变化的文件名?
监听必须重置 key,为什么?
如何监听多级子目录变化?
监听服务如何优雅关闭?
实际工作:监控日志文件变化
实际工作:监控配置文件热更新
10.6 模块六:NIO.2 异步 I/O(10 题)
NIO.2 异步 I/O 两种模式?
异步文件通道类名?
Future 模式异步读取
CompletionHandler 回调模式读取
异步写文件
异步网络通道:AsynchronousServerSocketChannel
异步 accept 连接
异步读取客户端消息
异步 I/O 适合什么业务场景?
异步 vs 阻塞 IO 优势?
10.7 模块七:企业实战场景(10 题)
批量备份当天生成的文件
文件去重(根据大小 + 修改时间)
目录定时清理(7 天前文件删除)
配置文件热加载
日志文件实时采集上传
文件上传后自动解压
递归统计目录占用空间
跨平台路径兼容处理
安全遍历(权限不足跳过)
文件完整性校验(读取并计算 MD5)
10.8 模块八:面试 / 高级综合题(5 题)
NIO.2 相比旧 IO 优势?
Path 和 File 区别?
Files 方法为什么都是静态?
WatchService 原理?
异步 I/O 在高并发文件服务中的应用
浙公网安备 33010602011771号