Java学习----NIO模型

发布于:2025-07-22 ⋅ 阅读:(22) ⋅ 点赞:(0)

        在 Java 的 I/O 模型中,NIO(Non - Blocking I/O,非阻塞 I/O)是对 BIO 的重要改进。它为高并发场景提供了更高效的处理方式,在众多 Java 应用中发挥着关键作用。

        NIO模型的核心在于非阻塞和多路复用,其采用 “一个线程处理多个连接” 的模式,主要依靠通道(Channel)、缓冲区(Buffer)和选择器(Selector)这三个核心组件协同工作,每个核心组件的功能原理和功能如下:

(1)通道:通道是数据传输的通道,类似于 BIO 中的流,但它是双向的,既可以从通道读取数据,也可以向通道写入数据。常见的通道有ServerSocketChannel(用于服务器端监听连接)、SocketChannel(用于客户端与服务器端的通信)等。​

(2)缓冲区:缓冲区是存储数据的容器,所有数据的读写都必须通过缓冲区进行。它本质上是一个数组,提供了对数据的结构化访问以及维护读写位置等信息。在 NIO 中,数据从通道读取到缓冲区,或从缓冲区写入到通道。​

(3)选择器:选择器是 NIO 实现多路复用的关键。它可以同时监控多个通道的事件(如连接请求、数据可读、数据可写等)。一个线程通过选择器注册多个通道,然后阻塞在选择器上,等待通道事件的发生。当有事件发生时,线程会处理这些事件,从而实现一个线程高效处理多个连接。​

        其工作流程大致为:服务器端通过ServerSocketChannel监听端口,将其注册到选择器上,并设置关注的事件(如接受连接事件)。客户端通过SocketChannel发起连接。选择器不断轮询注册的通道,当某个通道有事件发生时,就会被选中。线程从选择器中获取这些就绪的通道,进行相应的处理,如接受连接、读取数据、写入数据等,且这些操作大多是非阻塞的。​

        作为BIO的改进型,NIO也是有着许多优点,例如:​

(1)高并发处理能力强:借助多路复用机制,一个线程可以处理多个连接,大大减少了线程的创建和销毁带来的开销,以及线程上下文切换的成本,能在高并发场景下保持较好的性能。​

(2)非阻塞提升效率:在数据读写过程中,线程不会一直阻塞等待,当没有数据可读或可写时,线程可以去处理其他通道的事件,提高了线程的利用率。​

(3)双向传输更灵活:通道是双向的,相比 BIO 中流的单向传输,在一些需要双向数据交互的场景中,使用更方便灵活。​

        但是同样的,其缺点也不少,如:​

(1)编程复杂度高:NIO 的编程模型相对 BIO 更为复杂,需要理解通道、缓冲区、选择器等多个组件的协同工作机制,对开发者的技术要求较高。​

(2)学习门槛较高:其涉及的多路复用、非阻塞等概念较难理解,新手需要花费更多的时间和精力去掌握。​

(3)在低并发场景下优势不明显:在并发量较小的情况下,NIO 的优势难以体现,其复杂的机制可能还会带来一些额外的开销。​

        下面通过一个简单的客户端 - 服务器通信示例来展示 Java NIO 的使用。​

服务器端代码​

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NioServer {
    public static void main(String[] args) throws IOException {
        // 创建ServerSocketChannel并绑定端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(8888));
        // 设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 创建选择器
        Selector selector = Selector.open();
        // 将ServerSocketChannel注册到选择器,关注接受连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("服务器启动,监听端口8888...");

        while (true) {
            // 阻塞等待通道事件,返回就绪的通道数量
            int readyChannels = selector.select();
            if (readyChannels == 0) {
                continue;
            }

            // 获取就绪的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 移除处理过的事件,避免重复处理
                iterator.remove();

                if (key.isAcceptable()) {
                    // 处理接受连接事件
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = serverChannel.accept();
                    socketChannel.configureBlocking(false);
                    // 将客户端通道注册到选择器,关注数据可读事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("收到新的客户端连接:" + socketChannel.getRemoteAddress());
                } else if (key.isReadable()) {
                    // 处理数据可读事件
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = socketChannel.read(buffer);
                    if (bytesRead > 0) {
                        buffer.flip();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String message = new String(data);
                        System.out.println("收到客户端消息:" + message);

                        // 向客户端发送响应
                        buffer.clear();
                        String response = "服务器已收到消息:" + message;
                        buffer.put(response.getBytes());
                        buffer.flip();
                        socketChannel.write(buffer);
                    } else if (bytesRead == -1) {
                        // 客户端关闭连接
                        socketChannel.close();
                        System.out.println("客户端连接关闭");
                    }
                }
            }
        }
    }
}
/*
创建ServerSocketChannel并绑定端口,设置为非阻塞模式,然后注册到选择器上并关注接受连接事件。进入循环后,线程阻塞在选择器的select()方法上,等待通道事件。当有接受连接事件时,接受客户端连接,将客户端的SocketChannel注册到选择器并关注数据可读事件。当有数据可读事件时,从通道中读取数据,处理后向客户端发送响应。
*/

客户端代码​

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NioClient {
    public static void main(String[] args) throws IOException {
        // 打开SocketChannel并连接服务器
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8888));
        // 设置为非阻塞模式
        socketChannel.configureBlocking(false);

        System.out.println("已连接到服务器");

        // 向服务器发送数据
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要发送的消息:");
        String message = scanner.nextLine();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(message.getBytes());
        buffer.flip();
        socketChannel.write(buffer);

        // 读取服务器的响应
        buffer.clear();
        int bytesRead = socketChannel.read(buffer);
        if (bytesRead > 0) {
            buffer.flip();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            String response = new String(data);
            System.out.println("收到服务器响应:" + response);
        }

        scanner.close();
        socketChannel.close();
        System.out.println("客户端连接关闭");
    }
}
/*
创建SocketChannel连接服务器,设置为非阻塞模式,向服务器发送数据,然后读取服务器的响应,最后关闭连接。
*/

        从代码中能清楚看到 NIO 的非阻塞和多路复用特性,一个线程通过选择器处理多个通道的事件,极大地提高了并发处理能力。​


网站公告

今日签到

点亮在社区的每一天
去签到