当前位置: 首页 > news >正文

Java学习----NIO模型

        在 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 的非阻塞和多路复用特性,一个线程通过选择器处理多个通道的事件,极大地提高了并发处理能力。​

http://www.lryc.cn/news/595339.html

相关文章:

  • 识别PDF中的二维码
  • 软件中如何实现自动记忆上一次选的打印机(Python示例)
  • 数据结构 之 【排序】(直接插入排序、希尔排序)
  • 二分查找-35.搜索插入位置-力扣(LeetCode)
  • C语言-字符串数组
  • Vue过度与动画效果
  • FastAPI 中,数据库模型(通常使用 SQLAlchemy 定义)和接口模型(使用 Pydantic 定义的 schemas)的差异
  • Excel函数 —— TEXTJOIN 文本连接
  • 系统分析师-计算机系统-操作系统-存储器管理设备管理
  • LeafletJS 插件开发:扩展自定义功能
  • Java 实现 TCP 一发一收通信
  • 力扣面试150题--搜索二维矩阵
  • A316-Mini-V1:超小尺寸USB高清音频解码器模组技术探析
  • 解决 Ant Design v5.26.5 与 React 19.0.0 的兼容性问题
  • macOS 上安装 Kubernetes(k8s)
  • React 中使用immer修改state摆脱“不可变”
  • Ubuntu安装k8s集群入门实践-v1.31
  • HOT100——图篇Leetcode207. 课程表
  • Redis入门教程(一):基本数据类型
  • (LeetCode 每日一题) 1957. 删除字符使字符串变好 (字符串)
  • 17 BTLO 蓝队靶场 Pretium 解题记录
  • 【C++11】哈希表与无序容器:从概念到应用
  • 【Unity基础】Unity中2D和3D项目开发流程对比
  • 用户虚拟地址空间布局架构
  • git_guide
  • 【Git#6】多人协作 企业级开发模型
  • 【面经】实习经历
  • 深入理解 C++ 中的指针与自增表达式:*a++、(*a)++ 和 *++a 的区别解析
  • 破除扫描边界Photoneo MotionCam-3D Color 解锁动态世界新维度
  • 京东疯狂投资具身智能:众擎机器人+千寻智能+逐际动力 | AI早报