大白话讲Java NIO

如果把Java的I/O比作餐厅服务,BIO是“笨办法”(一对一盯桌),NIO是“聪明办法”(一个经理管所有桌)——核心就是用更少的人(线程)干更多的活(处理更多连接)。接下来用生活化的比喻+可视化图表,把NIO讲得明明白白。

一、BIO为啥“笨”?NIO为啥“聪明”?

🌰 生活化类比

场景BIO(阻塞I/O)NIO(非阻塞I/O)
餐厅对应角色每个餐桌配1个专属服务员(线程)1个大堂经理(Selector)+ 若干机动服务员(少量线程)
服务方式服务员站在餐桌旁,直到客人点餐/结账才走(线程阻塞等待I/O)经理在前台盯着所有餐桌,只有客人举手(I/O事件就绪)才派服务员过去(线程只处理有数据的连接)
效率100桌需要100个服务员(线程膨胀,成本高)100桌只需要1个经理+2-3个服务员(线程复用,成本低)
适用场景客人少、点完就走(低并发、短连接)客人多、边吃边加菜(高并发、长连接)

📊 可视化对比:BIO vs NIO工作流程

1. BIO工作流程(一对一盯桌)
客人点餐-有数据
客人没点餐-无数据
客人到店-客户端连接
分配专属服务员-创建线程
服务员等客人点餐-线程阻塞等数据
服务员上菜-线程处理I/O
客人离店-连接关闭
服务员空闲-线程销毁

BIO的问题:哪怕客人只是占着桌不点餐,服务员也得一直等,100个桌就需要100个服务员,人多了(线程多了),老板(CPU)要花大量精力协调,效率极低。

2. NIO工作流程(大堂经理统筹)
某桌举手-I/O事件就绪
没人举手-无事件
客人到店-客户端连接
登记到经理本上-注册Channel到Selector
经理盯着所有桌-Selector监听事件
派机动服务员处理-线程处理该连接
处理完回经理处-线程回到Selector

NIO的核心:经理(Selector)只盯“谁有需求”,服务员(线程)只处理有需求的桌,没人举手时经理就等着,服务员也不闲着——用极少的人搞定大量桌子。

二、NIO三大核心组件:经理、通道、餐盘

NIO的核心就是3个东西:Selector(大堂经理)、Channel(传菜通道)、Buffer(装菜的餐盘),三者配合才能实现“聪明服务”。

1. Buffer(餐盘):装数据的容器

🌰 类比

Buffer就像餐厅里的餐盘——菜(数据)不能直接拿手递,必须装在餐盘里传;客人点的菜(读数据)先放餐盘,服务员端走;要给客人上菜(写数据),也得先把菜装餐盘里。

📌 核心特点(通俗版)
  • 餐盘有固定大小(比如只能装10道菜)→ Buffer有capacity(容量),创建后不能改;
  • 装菜时从第一个空位开始装→ Buffer有position(当前装菜位置),装一个菜挪一格;
  • 装到指定位置就不能装了→ Buffer有limit(装菜上限),默认等于容量;
  • 装完菜要递给客人,得先把餐盘“转个向”→ Buffer的flip()方法(切换为“取菜模式”),把limit设为当前装菜位置,position归0,避免拿空位置。

2. Channel(传菜通道):数据的“双向路”

🌰 类比

Channel就像餐厅里的传菜通道——连接厨房(数据源)和餐桌(程序),而且是双向的(既能从厨房把菜传到餐桌,也能把餐桌的空盘子传回厨房);而BIO的“流”是单向的(要么只传菜,要么只收盘子)。

📌 核心特点(通俗版)
  • 双向通行:既能读数据(拿菜),也能写数据(送菜);
  • 必须走餐盘:不能直接拿手递菜,所有数据都要通过Buffer(餐盘)传输;
  • 可“喊人”:能告诉经理(Selector)“我这有菜要传/要取”(触发I/O事件)。
📋 常见的“传菜通道”
通道类型对应场景类比(餐厅)
FileChannel读写本地文件厨房和仓库之间的通道
SocketChannel客户端和服务器通信餐厅和客人桌之间的通道
ServerSocketChannel服务器监听连接餐厅门口的迎宾通道

3. Selector(大堂经理):事件“总控”

🌰 类比

Selector是餐厅的大堂经理——核心工作就是盯着所有传菜通道(Channel),只处理“有动静”的通道

  • 通道注册:所有通道都要在经理这“登记”,告诉经理“我要通知你我这有菜要传(读事件)/要装菜(写事件)”;
  • 监听事件:经理啥也不干,就盯着这些通道,直到有通道喊“我这有菜!”(事件就绪);
  • 分派任务:哪个通道有动静,就派服务员(线程)去处理这个通道的事,处理完服务员回来继续待命。
📊 Selector工作流程(可视化)
无通道有动静
有通道喊有菜
创建经理-Selector.open
打开传菜通道-Channel
通道设为非阻塞模式
通道找经理登记-register,说要监听读/写事件
经理继续盯通道
经理拿到有动静的通道列表
派服务员处理该通道的事-读/写数据
处理完,服务员回到经理身边

三、Selector底层模型

JVM给Selector做了一层“统一外壳”,底层会调用操作系统提供的“多路复用接口”,不同OS用不同的底层模型:

操作系统Java NIO底层使用的模型补充说明
Linux(2.6+)epoll(默认最优选择)高性能核心,NIO高并发的关键依赖
Linux(2.4及前)poll(降级选择,无epoll支持)老系统兼容,性能一般
WindowsIOCP(I/O Completion Port)不是select/poll/epoll,是Windows的“多路复用神器”,效率和epoll相当
macOS/BSDkqueue类似epoll的高效事件通知模型
其他嵌入式系统poll(若不支持epoll/kqueue)兼容优先,性能有限

在Linux操作系统底层有三种多路复用模型,分别是select、poll、epoll模型,在Linux2.6以后会自动选择epoll,2.4之前则是poll,以下是三种模型的解读:

  • select:https://blog.youkuaiyun.com/Tracycoder/article/details/148876497
  • poll:https://blog.youkuaiyun.com/Tracycoder/article/details/148892817
  • epoll:https://blog.youkuaiyun.com/Tracycoder/article/details/148894195
Java状态机模式可以想象成一个自动售货机。这个售货机有不同的状态,比如“等待投币”“已投币”等。在不同的状态下,售货机对顾客的操作会有不同的反应。 就像在“等待投币”状态时,顾客投币,售货机就会进入“已投币”状态;但如果顾客此时按出货按钮,售货机就不会出货,因为还没投币。当处于“已投币”状态时,顾客再投币,售货机可能会提示“已投币,无需再投”,而按出货按钮,售货机就会出货,然后回到“等待投币”状态。 在Java里,要实现这个状态机模式,首先得定义一个状态接口,就像引用[1]里的 `VendingMachineState` 接口,它包含了在不同状态下售货机的行为,比如 `insertCoin()`(投币)和 `dispenseItem()`(出货)。然后为每个状态创建具体的类来实现这个接口,在具体的类里实现对应状态下的行为逻辑。 ```java // 状态接口 public interface VendingMachineState { void insertCoin(); void dispenseItem(); } // 等待投币状态 class WaitingForCoinState implements VendingMachineState { @Override public void insertCoin() { System.out.println("已投币,进入可出货状态"); // 这里可以添加状态切换的逻辑 } @Override public void dispenseItem() { System.out.println("未投币,无法出货"); } } // 已投币状态 class CoinInsertedState implements VendingMachineState { @Override public void insertCoin() { System.out.println("已投币,无需再投"); } @Override public void dispenseItem() { System.out.println("出货中..."); // 这里可以添加状态切换回等待投币状态的逻辑 } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TracyCoder123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值