1. 引言
本文将梳理Java IO功能在不同版本中的演进历程。首先介绍初始Java版本中的java.io
包,接着分析Java 1.4引入的java.nio
包,最后探讨Java 1.7新增的java.nio.file
包(即NIO.2)。
2. Java NIO包
首个Java版本通过java.io
包提供了File
类访问文件系统。**File
类用于表示文件和目录,但仅提供有限的文件系统操作**,如创建/删除文件、检查存在性、读写权限验证等。
但该设计存在明显缺陷:
- 缺少拷贝方法——需手动创建两个
File
实例,通过缓冲区读写实现文件拷贝 - 错误处理不友好——部分方法用
boolean
返回操作结果,而非异常机制 - 文件属性支持有限——仅能获取名称、路径、读写权限、大小等基础属性
- 阻塞式API——线程会因IO操作被完全阻塞
使用传统IO读取文件的示例:
@Test
public void readFromFileUsingFileIO() throws Exception {
File file = new File("src/test/resources/nio-vs-nio2.txt");
FileInputStream in = new FileInputStream(file);
StringBuilder content = new StringBuilder();
int data = in.read();
while (data != -1) {
content.append((char) data);
data = in.read();
}
in.close();
assertThat(content.toString()).isEqualTo("Hello from file!");
}
为解决上述问题,Java 1.4引入了java.nio
包(New IO)。NIO的核心改进包含三个组件:Channel
、Buffer
和Selector
。
2.1. Channel
Java NIO Channel是用于读写缓冲区的双向通道。与传统IO流(单向)相比,它具有显著差异:
- 支持双向数据传输
- 可实现异步读写
主要实现类包括:
-
FileChannel
:文件系统读写 -
DatagramChannel
:UDP网络通信 -
SocketChannel
:TCP网络通信
2.2. Buffer
Buffer本质是内存块的数据容器,提供了操作内存块的实用方法。理解其三个核心属性是关键:
- Capacity(容量):内存块固定大小,写满后需清空或读取
- Position(位置):写操作的起始索引(初始0),读操作时作为读取起点
- Limit(界限):控制读写范围
Buffer类型覆盖所有Java基本类型(除Boolean外),包括MappedByteBuffer
等特殊实现。
常用方法速览:
-
allocate(int size)
:创建指定大小的缓冲区 -
flip()
:切换写模式到读模式 -
clear()
:清空整个缓冲区 -
compact()
:仅清除已读部分 -
rewind()
:重置position为0(支持重复读取)
使用Channel和Buffer读取文件的示例:
@Test
public void readFromFileUsingFileChannel() throws Exception {
RandomAccessFile file = new RandomAccessFile("src/test/resources/nio-vs-nio2.txt", "r");
FileChannel channel = file.getChannel();
StringBuilder content = new StringBuilder();
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
content.append((char) buffer.get());
}
buffer.clear();
bytesRead = channel.read(buffer);
}
file.close();
assertThat(content.toString()).isEqualTo("Hello from file!");
}
2.3. Selector
Java NIO Selector支持单线程管理多个通道。使用要点:
- 通道必须处于非阻塞模式
- 通过
register()
注册通道,返回SelectionKey
对象 select()
检测就绪通道数量selectedKeys()
获取所有就绪通道
2.4. NIO包的局限性
尽管NIO解决了阻塞问题,但仍存在明显短板:
- 符号链接支持薄弱
- 文件属性访问能力有限
- 缺乏高级文件系统管理工具
3. Java NIO.2包
Java 1.7推出的java.nio.file
包(即NIO.2)引入了重大改进:
- 异步IO支持(NIO包缺失的能力)
- 高级文件操作:通过
Files
、Path
、Paths
类实现 - 底层增强:新增
AsynchronousFileChannel
等组件
Path对象表示由分隔符连接的目录和文件层次结构,提供:
- 路径解析方法(如
getFileName()
、getParent()
) - 路径构造工具(
resolve()
、relativize()
)
Files类提供基于Path的高阶文件操作:
- 文件/目录/符号链接管理
- 通过
readAttributes()
读取文件属性
使用NIO.2读取文件的简单粗暴实现:
@Test
public void readFromFileUsingNIO2() throws Exception {
List<String> strings = Files.readAllLines(Paths.get("src/test/resources/nio-vs-nio2.txt"));
assertThat(strings.get(0)).isEqualTo("Hello from file!");
}
4. 总结
本文梳理了java.nio
与java.nio.file
包的核心差异:
- NIO包:提供低级非阻塞IO能力(Channel/Buffer/Selector)
- NIO.2包:专注于高级文件系统操作(Files/Path)和异步IO
两者并非替代关系,而是互补设计。实际开发中常结合使用:NIO处理网络通信,NIO.2处理文件操作。所有示例代码可在GitHub仓库获取。