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的核心改进包含三个组件:ChannelBufferSelector

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支持单线程管理多个通道。使用要点:

  1. 通道必须处于非阻塞模式
  2. 通过register()注册通道,返回SelectionKey对象
  3. select()检测就绪通道数量
  4. selectedKeys()获取所有就绪通道

2.4. NIO包的局限性

尽管NIO解决了阻塞问题,但仍存在明显短板:

  • 符号链接支持薄弱
  • 文件属性访问能力有限
  • 缺乏高级文件系统管理工具

3. Java NIO.2包

Java 1.7推出的java.nio.file包(即NIO.2)引入了重大改进:

  • 异步IO支持(NIO包缺失的能力)
  • 高级文件操作:通过FilesPathPaths类实现
  • 底层增强:新增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.niojava.nio.file包的核心差异:

  • NIO包:提供低级非阻塞IO能力(Channel/Buffer/Selector)
  • NIO.2包:专注于高级文件系统操作(Files/Path)和异步IO

两者并非替代关系,而是互补设计。实际开发中常结合使用:NIO处理网络通信,NIO.2处理文件操作。所有示例代码可在GitHub仓库获取。


原始标题:What Is the Difference Between NIO and NIO.2?