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

JAVA NIO学习笔记基础强化学习总结

基本概念

通过前文的学习,我们用java.net包下的API实现了同步阻塞IO模型,我们用java.nio包下的API实现了同步非阻塞IO和IO多路复用模型。

Java NIO(New I/O 或者是 none-block IO)是 Java 1.4 引入的非阻塞 I/O 模型,旨在解决传统阻塞 I/O(BIO)在高并发场景下的性能瓶颈。这些API的底层在JVM层面包装的是对一些系统函数的调用,供给开发者使用的API。

包含了三大组件:通道(Channel) 缓冲区(Buffer)  选择器(Selector)


Channel

Channel是数据传输的双向通道,与流(InputStream/OutputStream)不同,Channel同时支持读写操作。

ServerSocketChannel和SocketChannel是网络读写的Channel:

ServerSocketChannel底层封装了一个服务端的Socket,SocketChannel则封装了一个客户端Socket。

FileChannel则代表了文件读写

ServerSocketChannel:服务器连接监听

核心功能

  • 绑定本地端口,监听客户端连接请求
  • 通过accept()方法来接受客户端连接,返回SocketChannel用于后续通信
  • 可以配置非阻塞模式,与Selector配合实现多路复用

用于服务端接收客户端连接使用,相关API如下:

初始化
工厂模式创建ServerSocketChannel,增强扩展性
ServerSocketChannel serverChannel = ServerSocketChannel.open();
绑定本地端口
serverChannel.bind(new InetSocketAddress(8080));
配置非阻塞模式,默认是阻塞模式
serverChannel.configureBlocking(false);
=================================================================
接收连接
阻塞模式:若无连接请求,线程会一直阻塞直到有连接到达
SocketChannel clientChannel = serverChannel.accept();
非阻塞模式:立即返回,没有连接请求返回null
SocketChannel clientChannel = serverChannel.accpet();
if(clientChannel != null){// 通常也将客户端设置为非阻塞模式clientChannel.configureBlocking(false);
}
=================================================================
关闭通道 释放资源
serverChannel.close();

SocketChannel:客户端与服务端数据通信

核心功能:

  • 客户端通过connect()连接服务端
  • 服务端通过ServerSocketChannel.accept()返回的SocketChannel与客户端通信
  • 支持双向数据读写
  • 支持非阻塞模式,可以与Selector配合实现异步IO

主要是用于双向通信,相关API如下

创建与连接
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
boolean connected = clientChannel.connect(new InetSocketAddress("localhost",8080));
// 非阻塞模式下,连接可能未立即完成,需检查finishConnect()
if (!connected) {while (!clientChannel.finishConnect()) {// 等待连接完成(可在此处理其他任务)}
}
===========================================================================
数据读写
写入数据(返回写入的字节数)
ByteBuffer buffer = ByteBuffer.wrap("Hello, Server!".getBytes());
int bytesWritten = clientChannel.write(buffer);读取数据(返回读取的字节数,可能为0)
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(readBuffer);
if (bytesRead > 0) {readBuffer.flip();byte[] data = new byte[bytesRead];readBuffer.get(data);System.out.println("Received: " + new String(data));
}
===========================================================================
关闭通道 释放资源
clientChannel.close()
组件核心功能关键API
ServerSocketChannel服务端监听端口;接受客户端连接open()bind()accept()configureBlocking(false)
SocketChannel客户端连接服务端;双向数据通信open()connect()read()write()finishConnect()configureBlocking(false)

Buffer

数据缓冲区,也是一个数据容器,在JAVA NIO中数据都是用Buffer存储的。Channel读取接收到的数据先写到Buffer中,应用程序再从Buffer中读取,Channel写数据也是先写到Buffer中。

为什么要使用缓冲区?

为了通过 Buffer 的批量操作,将 多次小数据量系统调用 合并为 单次大数据量系统调用,减少内核态与用户态切换次数,显著提高网络通信的性能。

ByteBuffer是里面最常用的一个类型,因为字节类型最通用传输最高效。

ByteBuffer

创建ByteBuffer

allocate(int capacity)方法

创建的是一个HeapByteBuffer实例,数据是存储在堆区按容量开辟空间的字节数组中。

  • GC 管理:无需手动释放内存,但 GC 可能带来停顿(STW)。
  • 访问速度:Java 对象直接访问,速度快(但受 JVM 堆限制)。
  • 适用场景:大多数常规 I/O 操作,尤其是数据量较小或需要频繁 GC 的场景。

allocateDirect(int capacity)方法

创建的是一个DirectByteBuffer实例,数据是存储在操作系统直接内存中按容量开辟空间的字节数组中。

  • 手动管理:虽然 DirectByteBuffer 对象本身由 GC 管理,但堆外内存需通过 Cleaner 机制在 GC 时释放(或显式调用 sun.misc.Unsafe.freeMemory())。
  • 访问速度
    • 写入:比堆缓冲区慢(需通过 JNI 调用本地方法写入堆外内存)。
    • 读取:比堆缓冲区快(尤其是涉及 I/O 操作时,可避免数据拷贝)。
  • 适用场景:高频、大容量的 I/O 操作(如网络传输、文件读写),尤其是需要零拷贝的场景。
堆缓冲区(HeapByteBuffer)直接缓冲区(DirectByteBuffer)
内存分配JVM 堆内存(GC 管理)操作系统本地内存(需手动释放)
分配/释放速度快(纯 Java 操作)慢(需调用本地方法 malloc/free
I/O 性能低(额外拷贝)高(零拷贝优化)
访问速度快(Java 对象直接访问)慢(需通过 JNI 访问堆外内存)
内存限制受 JVM 堆大小限制(-Xmx)受操作系统可用内存限制(可分配 GB 级)
适用场景小数据量、频繁 GC 的场景大数据量、高频 I/O、零拷贝场景
操作ByteBuffer

Buffer类中定义了四个索引,这些索引记录了底层数据元素的状态。

capacity:缓冲区可以容纳的数据元素的最大数量

limit:停止读写的索引

position:当前要读或写的索引,读写操作是从position开始,到limit停止

mark:用于记录position的位置,默认值为-1

flip():由写模式切换为读模式 通过limit = position

compact():由读模式切换为写模式

remaining():计算可读/写大小

操作触发方法索引调整模式变化方向
写模式→读模式flip()limit = position; position=0写 → 读(单向)
读模式→写模式clear()position=0; limit=capacity读 → 写(完全重置)
读模式→写模式compact()移动未读数据;调整position/limit读 → 写(保留未读数据)
1. 向 ByteBuffer 中写数据:核心方法是put
  • put(byte[] src)
  • put(byte[] src, int offset, int length)
  • put(ByteBuffer src)
2. 从 ByteBuffer 中读数据:核心方法是 get
  • get(byte[] dst)
  • get(byte[] dst, int offset, int length)

Selector选择器

允许单个线程高效管理多个通道(Channel),实现I/O多路复用,从而显著提升网络应用的性能和可扩展性。

selector底层封装的是IO多路复用模型 epoll ,主要功能是将Channel注册到Selector上并指定要监听的事件,selector会不断轮询注册在其上的Channel,检测其事件是否就绪;Selector每次返回已就绪Channel对应的SelectionKey,通过SelectionKey可获取事件的详细信息,然后进行后续的操作。(单线程通过事件驱动监控多个通道,仅当通道发生指定事件(如可读,可写,新连接)时才处理,避免空轮询和线程切换)

Selector支持四种事件类型,通过SelectionKey的常量定义:

  • OP_ACCEPT:服务端接收新连接
  • OP_CONNECT:客户端完成连接
  • OP_READ:通道数据可读
  • OP_WRITE:通道可写(谨慎使用,避免频繁触发)

使用Selector线程循环需要以下步骤:

  1. 调用 select()阻塞当前线程 等待事件发生
  2. 遍历就绪事件(selectedKeys),分发处理
  3. 避免线程阻塞在 I/O 操作上面,充分利用空闲事件处理其他通道

相关 API 如下:

Selector类:

创建Selector实例,底层调用系统默认的SelectorProvider(如Linux的EPollSelectorProvider)。
Selector.open()
阻塞等待事件,超时返回。返回值为就绪通道数量。
select(long timeout)
返回就绪事件的SelectionKey集合,需手动遍历处理。
selectedKeys()
唤醒阻塞的select(),用于优雅退出事件循环。
wakeup()

SelectionKey类:

设置通道感兴趣的事件(如OP_READ | OP_WRITE)。
interestOps(int ops)
获取当前就绪的事件(如OP_READ)。
readyOps()
绑定用户对象(如Socket实例),便于事件处理时获取上下文。
attach(Object obj)
检查键是否有效(如通道关闭后键自动失效)。
isValid()

NIO完整案例

根据上述学的三大组件写一个完整的Java NIO 案例

/*** @author XJunFy*/
public class NioServer {public static void main(String[] args) {try {ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(9999));serverSocketChannel.configureBlocking(false);Selector selector = Selector.open();SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while(true){int select = selector.select();Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();while (keyIterator.hasNext()){SelectionKey key = keyIterator.next();keyIterator.remove();processingKey(key,selector);}}} catch (IOException e) {throw new RuntimeException(e);}}private static void processingKey(SelectionKey key, Selector selector) throws IOException {if (key.isValid()){if (key.isAcceptable()){ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);socketChannel.register(selector,SelectionKey.OP_READ);return;}if (key.isReadable()){SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer readBuffer = ByteBuffer.allocate(1024);int read = socketChannel.read(readBuffer);if (read > 0){readBuffer.flip();byte[] bytes = new byte[readBuffer.remaining()];readBuffer.get(bytes);String msg = new String(bytes, Charset.defaultCharset());System.out.println("服务端收到来自客户端的数据:" + msg);ByteBuffer sendBuffer = ByteBuffer.allocate(256);sendBuffer.put("hello nio client,i am nio server\n".getBytes(StandardCharsets.UTF_8));sendBuffer.flip();socketChannel.write(sendBuffer);}return;}}}
}


文章转载自:

http://cGrjjE1K.mjjty.cn
http://0JCy39p2.mjjty.cn
http://G9OtGbwH.mjjty.cn
http://eu8gux3d.mjjty.cn
http://rmAblIKZ.mjjty.cn
http://8mcoqCzS.mjjty.cn
http://D2UEqM9W.mjjty.cn
http://oR9OcHVp.mjjty.cn
http://pQ9MRFHv.mjjty.cn
http://WdnEzBW6.mjjty.cn
http://fkJbzLgi.mjjty.cn
http://GFeVP9to.mjjty.cn
http://kYlS1JbL.mjjty.cn
http://2f1vnKaf.mjjty.cn
http://SHvold47.mjjty.cn
http://KVtSRJ1a.mjjty.cn
http://V3TSPPsZ.mjjty.cn
http://PnlKLbtI.mjjty.cn
http://oWyMy7o4.mjjty.cn
http://qtH8De03.mjjty.cn
http://iIpYcN3S.mjjty.cn
http://q9yRaW5K.mjjty.cn
http://5oy32QOc.mjjty.cn
http://EUNjYGmM.mjjty.cn
http://VG5FhM0Z.mjjty.cn
http://uNjkgvCz.mjjty.cn
http://cuA0DER2.mjjty.cn
http://SVx8rwtj.mjjty.cn
http://F2ImiWdq.mjjty.cn
http://wBEC2R2p.mjjty.cn
http://www.dtcms.com/a/371323.html

相关文章:

  • 基于51单片机手机无线蓝牙APP控制风扇调速设计
  • 力扣hot100:相交链表与反转链表详细思路讲解(160,206)
  • 如何在 DevOps 管道中实现 AI?
  • 【Java基础07】面向对象进阶
  • 动态维护有效区间:滑动窗口
  • 桌面时间 Catime
  • 解锁服务器网络配置新姿势:Wisdom SSH 助力之旅
  • 设计模式:状态模式(State Pattern)
  • 【ARM基础知道】
  • SpringCloud Alibaba微服务--Gateway使用
  • 基于脚手架微服务的视频点播系统-播放控制部分
  • 【C++详解】C++ 智能指针:使用场景、实现原理与内存泄漏防治
  • 【iOS】push,pop和present,dismiss
  • HiCMAE 论文复现:基于 RAVDESS 数据集的音视频情感识别
  • axios的两种异步方式对比
  • uniapp结合uview制作美食页面
  • Spark mapreduce 的一个用法
  • [iOS] push 和 present Controller 的区别
  • 五.贪心算法
  • vue中axios与fetch比较
  • 【iOS】block复习
  • 打造第二大脑读书笔记目录
  • 【Docker】Docker基础
  • 一、CMake基础
  • 【音视频】WebRTC P2P、SFU 和 MCU 架构
  • VBA 自动转化sheet到csv文件
  • rabbitmq 重试机制
  • 《C++进阶之STL》【set/map 使用介绍】
  • 【RabbitMQ】----初识 RabbitMQ
  • WebRTC开启实时通信新时代