Java NIO(New I/O)

一、理论说明

1. NIO 的定义

Java NIO 是从 Java 1.4 版本开始引入的一套新的 I/O API,它提供了与标准 I/O 不同的工作方式。NIO 以块(Buffer)为基本处理单位,采用非阻塞(Non-blocking)模式,并引入了选择器(Selector)机制,使得单线程可以同时处理多个 I/O 通道,大幅提高了 I/O 效率,尤其适用于高并发场景。

2. NIO 与传统 I/O 的区别

特性 传统 I/O(java.io NIO(java.nio)
处理方式 基于流(Stream),单向传输 基于缓冲区(Buffer),双向传输
阻塞模式 阻塞 I/O(BIO) 非阻塞 I/O(NIO)
通道 有(Channel)
选择器 有(Selector),支持多路复用
面向对象 面向字节流或字符流 面向缓冲区和通道
适用场景 连接数目少且固定的架构 连接数目多且短的架构(如聊天服务器)

二、核心组件

1. 缓冲区(Buffer)

缓冲区是一个用于存储特定基本类型数据的容器,所有数据都通过缓冲区处理。主要缓冲区类型有:ByteBufferCharBufferIntBuffer等,最常用的是ByteBuffer

复制代码
import java.nio.ByteBuffer;

public class BufferExample {
    public static void main(String[] args) {
        // 创建容量为10的ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(10);
        
        // 写入数据
        buffer.put((byte) 'H');
        buffer.put((byte) 'e');
        buffer.put((byte) 'l');
        buffer.put((byte) 'l');
        buffer.put((byte) 'o');
        
        // 切换为读模式
        buffer.flip();
        
        // 读取数据
        while(buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        // 输出: Hello
    }
}

缓冲区的三个核心属性:

  • capacity:缓冲区的最大容量
  • position:当前读写位置
  • limit:读写的上限(写模式下等于 capacity,读模式下等于实际写入的数据量)

2. 通道(Channel)

通道是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。通道与流的不同之处在于通道是双向的,而流是单向的(只能读或写)。主要通道类型有:

  • FileChannel:用于文件读写
  • SocketChannel:用于 TCP 客户端
  • ServerSocketChannel:用于 TCP 服务器
  • DatagramChannel:用于 UDP 通信

3. 选择器(Selector)

选择器是 NIO 的核心,它允许单线程处理多个通道。通过注册通道到选择器并监听各种事件(如连接、接受、读、写),实现了单线程管理多个客户端连接的能力。

复制代码
import java.nio.channels.*;
import java.io.IOException;

public class SelectorExample {
    public static void main(String[] args) throws IOException {
        // 创建选择器
        Selector selector = Selector.open();
        
        // 创建通道并注册到选择器
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new java.net.InetSocketAddress(8080));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        // 轮询选择器
        while (true) {
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;
            
            // 处理就绪的通道
            java.util.Set<SelectionKey> selectedKeys = selector.selectedKeys();
            java.util.Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                
                if (key.isAcceptable()) {
                    // 处理新连接
                } else if (key.isReadable()) {
                    // 处理读事件
                }
                
                keyIterator.remove();
            }
        }
    }
}

三、文件操作示例

以下是使用 NIO 进行文件复制的示例:

复制代码
import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class FileCopyExample {
    public static void main(String[] args) {
        try (FileInputStream fin = new FileInputStream("source.txt");
             FileOutputStream fout = new FileOutputStream("target.txt");
             FileChannel inChannel = fin.getChannel();
             FileChannel outChannel = fout.getChannel()) {
            
            // 创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            // 读取数据到缓冲区,然后写入目标通道
            while (inChannel.read(buffer) != -1) {
                buffer.flip();  // 切换为读模式
                outChannel.write(buffer);
                buffer.clear(); // 清空缓冲区,准备下一次读取
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

四、网络编程示例(非阻塞服务器)

复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioServer {
    private static final int PORT = 8080;
    private Selector selector;
    private ServerSocketChannel serverChannel;

    public NioServer() {
        try {
            // 初始化选择器和服务器通道
            selector = Selector.open();
            serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);
            serverChannel.socket().bind(new InetSocketAddress(PORT));
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器启动,监听端口: " + PORT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        try {
            while (true) {
                // 阻塞等待就绪的通道
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();
                
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    
                    if (key.isAcceptable()) {
                        // 处理新连接
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = ssc.accept();
                        clientChannel.configureBlocking(false);
                        clientChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());
                    } else if (key.isReadable()) {
                        // 处理读事件
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int bytesRead = clientChannel.read(buffer);
                        
                        if (bytesRead > 0) {
                            buffer.flip();
                            byte[] data = new byte[buffer.remaining()];
                            buffer.get(data);
                            String message = new String(data, "UTF-8");
                            System.out.println("收到消息: " + message);
                            
                            // 回显消息给客户端
                            ByteBuffer response = ByteBuffer.wrap(("服务器已收到: " + message).getBytes());
                            clientChannel.write(response);
                        } else if (bytesRead == -1) {
                            // 客户端关闭连接
                            System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());
                            clientChannel.close();
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new NioServer().start();
    }
}

五、面试题

题目:

答案:

六、自我总结

Java NIO 通过引入缓冲区、通道和选择器,提供了一种高效的 I/O 处理方式,尤其适合高并发场景。相比传统 I/O,NIO 的主要优势在于:

  1. 非阻塞 I/O 允许单线程处理多个连接,减少线程开销
  2. 选择器机制实现了高效的事件驱动模型
  3. 缓冲区操作简化了数据处理流程
  4. 通道支持双向数据传输

然而,NIO 的编程模型相对复杂,需要理解缓冲区状态转换、事件轮询等概念。在实际应用中,应根据具体场景选择合适的 I/O 方式:

  • 对于连接数少且稳定的场景,传统 I/O 可能更简单
  • 对于高并发、短连接的场景,NIO 是更好的选择
  • 对于超高性能需求,可以考虑使用 NIO 2.0(AIO,异步 I/O)

NIO 是 Java 网络编程的核心技术之一,广泛应用于各种高性能服务器、中间件和分布式系统中。

相关推荐
要开心吖ZSH13 分钟前
《Spring 中上下文传递的那些事儿》Part 4:分布式链路追踪 —— Sleuth + Zipkin 实践
java·分布式·spring
桦说编程32 分钟前
深入解析CompletableFuture源码实现
java·性能优化·源码
傻啦嘿哟34 分钟前
Python 办公实战:用 python-docx 自动生成 Word 文档
开发语言·c#
翻滚吧键盘39 分钟前
js代码09
开发语言·javascript·ecmascript
q567315231 小时前
R语言初学者爬虫简单模板
开发语言·爬虫·r语言·iphone
蓝澈11211 小时前
迪杰斯特拉算法之解决单源最短路径问题
java·数据结构
Kali_071 小时前
使用 Mathematical_Expression 从零开始实现数学题目的作答小游戏【可复制代码】
java·人工智能·免费
rzl021 小时前
java web5(黑马)
java·开发语言·前端
时序数据说2 小时前
为什么时序数据库IoTDB选择Java作为开发语言
java·大数据·开发语言·数据库·物联网·时序数据库·iotdb
jingling5552 小时前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架