Java IO模型

发布时间:2026/6/13 20:13:17

Java IO模型 I/O模型1. NIO全称为Non-blocking I/O或New I/O 非阻塞输入/输出是 Java 1.4 中引入的新 I/O 模型它与传统 I/OOld I/O即 BIO有着本质的区别主要用于高并发、高吞吐量的网络通信和文件操作有三大核心组件Buffer、Channel、Selector1.1. Buffer 缓冲区传统 I/O是面向“流”的数据像水流一样单向流动无法前后移动NIO是面向“缓冲区”的数据被读取到一个缓冲区中可以在缓冲区中前后移动通过position,limit,capacity等指针控制常见缓冲区类型有ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBufferByteBuffer最常用MappedByteBufferDirectByteBufferHeapByteBuffer1.2. Channel 通道传统 I/O 通过Stream读写数据流是单向的分为InputStream和OutputStreamNIO 的Channel是双向的可以同时进行读和写操作Channel 可以从 Buffer 中读取数据也可以把数据写入 Buffer常见的 Channel 有FileChannel文件、SocketChannelTCP 客户端、ServerSocketChannelTCP 服务端1.3. Selector 选择器Selector 被称为“多路复用器”是 NIO 实现非阻塞的核心它可以同时监听多个 Channel 的事件如连接建立、数据到达、写入完成等通过 Selector一个单独的线程就可以管理多个 Channel成百上千个网络连接极大地减少了线程上下文切换的开销selector 版threadselectorchannelchannelchannel2. BIO全称为Blocking I/O阻塞式输入/输出是 Java 早期JDK 1.4 之前唯一的网络通信模型无论是客户端还是服务端在进行网络读写操作时如果数据没有准备好线程就会被“卡住”阻塞直到数据就绪并完成操作2.1. 工作模式在 BIO 模型中服务端的典型工作流程如下ServerSocket 绑定端口服务端创建ServerSocket监听指定端口阻塞等待连接调用serverSocket.accept()方法线程被阻塞直到有新的客户端连接进来。一旦有连接accept()返回一个Socket对象阻塞等待数据通过Socket获取输入流调用inputStream.read()方法线程再次被阻塞直到客户端发送来数据并读取完毕处理与响应业务逻辑处理然后通过输出流outputStream.write()将结果返回给客户端写操作也可能阻塞循环往复处理完一个请求后继续调用accept()等待下一个连接2.2. 线程模型2.2.1. 一请求一线程为了解决单线程无法并发的问题传统的 BIO 采用了**“一请求一线程”**的模型主线程专门负责调用accept()监听连接每当accept()返回一个Socket就新建一个工作线程去处理这个Socket的读写和业务逻辑多线程版threadsocket1threadsocket2threadsocket3缺点内存占用高每次都要新建线程如果并发很高理论上会无限制的创建线程线程上下文切换成本高只适合连接数少的场景2.2.2. 线程池未解决方案一中线程资源频繁创建回收的问题引入了线程池版方案线程池版threadsocket1threadsocket2socket3socket4缺点阻塞模式下线程仅能处理一个 socket 连接仅适合短连接场景2.3. 致命缺陷如果按照上述流程服务端是单线程的当线程在为客户端 A 执行read()等待数据时如果客户端 A 迟迟不发送数据线程就会一直卡在 A 身上。此时客户端 B 尝试连接服务端根本无力响应因为无法执行到accept()虽然“多线程”解决了单线程无法并发的问题但它带来了极其严重的性能瓶颈这也是后来 NIO 诞生的根本原因2.3.1. 线程资源极度浪费并发量受限在 BIO 中如果客户端建立连接后不发送数据或者网络很慢处理该连接的工作线程就会一直阻塞在read()上。这意味着一个线程即使什么活都没干也被独占了服务器的线程数是极其宝贵的系统资源通常几百上千个就顶天了如果有一万个空闲连接BIO 就需要一万个线程服务器直接 OOM 崩溃2.3.2. 频繁的上下文切换开销当活跃连接数较多时JVM 需要在大量阻塞的线程之间进行调度和切换。线程上下文切换是非常耗费 CPU 资源和时间的操作导致系统整体吞吐量急剧下降2.3.3. 连接建立与数据读取耦合在 BIO 中accept()和read()都是阻塞的且必须由同一个执行流来串行处理。没有机制能够做到“我先去干点别的等有数据了再来读”2.4. BIO vs NIO为了更深刻地理解 BIO 的局限我们对比一下 NIO特性BIO (Blocking I/O)NIO (Non-blocking I/O)核心模型面向流面向缓冲区阻塞特性阻塞式accept/read/write都会卡住线程非阻塞式read没数据直接返回线程可做其他事并发策略一请求一线程一个线程可管理多请求基于 Selector 多路复用适用场景连接数少且固定、业务处理耗时长的架构如传统后台管理连接数多、但每个连接活跃度低长连接但数据少的架构如聊天服务器、高并发网关打个通俗的比方BIO 就像传统餐厅的服务员一个服务员全程只服务一桌客人。客人看菜单时服务员傻站着等阻塞客人点菜后服务员又去后厨傻等出菜。如果餐厅来了100桌客人就需要100个服务员哪怕其中90桌只是在聊天还没点菜服务员也被占着NIO 就像现代餐厅的大堂经理大堂经理手里拿着对讲机在餐厅里巡视。哪桌客人举手了连接有数据可读/可写经理就安排对应的人员去处理一下处理完继续巡视。一个经理就能照看整个餐厅的上百桌客人3. AIO全称为Asynchronous I/O异步输入/输出在 Java 中也被称为NIO 2.0它是自 JDK 7 引入的全新 I/O 模型旨在解决 Java 原生 NIO 编程复杂度高的问题并实现真正的底层异步网络通信与文件操作理解 AIO 的核心是两个词真正异步和回调驱动3.1. 前置概念同步应用程序发起 I/O 调用后必须亲自参与等待数据就绪或把数据从内核空间拷贝到用户空间的过程。哪怕像 NIO 那样不阻塞线程但数据拷贝阶段仍需应用程序自己轮询或触发依然是同步的异步应用程序发起 I/O 调用后立刻返回完全可以去做别的事情。底层的操作系统负责等待数据就绪并将数据从内核空间拷贝到用户空间。完成后操作系统会主动通知应用程序通常通过回调函数AIO 的核心组件是Future和CompletionHandler完成回调Future 模型发起操作后返回一个Future对象你可以稍后调用future.get()阻塞等待结果这其实退化为同步了较少作为核心使用CompletionHandler 回调模型这是 AIO 的精髓。发起操作时传入一个回调对象当 I/O 操作彻底完成数据已拷贝到用户空间时系统自动调用回调对象中的方法3.2. 工作模式AIO 的网络通信和文件操作使用了不同的核心类网络 AIOAsynchronousServerSocketChannel服务端、AsynchronousSocketChannel客户端文件 AIOAsynchronousFileChannelAIO 服务端的典型工作流程创建通道并监听打开AsynchronousServerSocketChannel绑定端口发起接受连接请求调用channel.accept()此时不会阻塞。你需要传入一个CompletionHandler系统接管操作系统在底层监听连接。当有客户端连接时操作系统完成连接建立并自动触发你传入的CompletionHandler的completed()方法在回调中发起读操作在completed()方法中你拿到客户端的Channel再次调用channel.read()读取数据并再次传入一个新的CompletionHandler系统再次接管操作系统等待数据到达并拷贝到 Buffer 中完成后触发读回调的completed()方法处理与响应在读回调中处理业务逻辑并调用channel.write()写回数据同样可以传入回调关键点整个过程中Java 线程只负责“发起请求”和“在回调中处理结果”所有的“等待”和“数据搬运”全部由操作系统内核完成3.3. AIO vs NIO维度NIO (Non-blocking I/O)AIO (Asynchronous I/O)核心模型同步非阻塞真正异步操作系统依赖依赖 Linux 的epoll依赖 Linux 的io_uring(新) 或 Windows 的IOCP谁负责数据拷贝应用程序。操作系统通知数据就绪后应用线程仍需自己调用read()把数据从内核拷贝到用户空间操作系统。操作系统在后台默默完成数据拷贝拷贝完毕后才通知应用程序来取编程范式轮询/事件触发。需要 Selector 不断select()在代码里判断是读事件还是写事件并处理回调驱动。发起请求后不管完成时系统自动调用completed()方法编程复杂度较高。需处理半包、粘包、空轮询 Bug 等复杂问题极高原生 API。回调地狱状态管理极其困难打个通俗的比方NIO 像去干洗店洗衣服你把衣服放下拿到一个小票。你不需要在店里傻等非阻塞你可以去逛街。但你得时不时掏出小票看看轮询/Selector一旦发现衣服洗好了你得自己把衣服拿走数据拷贝AIO 像叫外卖你下单后直接去打游戏异步。外卖小哥不仅把饭做好了还亲自把饭送到你嘴里内核完成拷贝然后拍拍你的肩膀说“饭到了吃吧”触发回调3.4. AIO 的真正主场文件 I/O虽然 AIO 在网络通信中败给了 NIO配合 Netty但它在文件 I/O领域却是王者对于大文件的异步读写AsynchronousFileChannel是 Java 中唯一的标准异步文件操作 API因为文件 I/O 通常比较耗时交给操作系统在后台异步完成对主线程的影响最小

相关新闻