news 2026/6/13 20:12:58

Java IO模型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java IO模型

I/O模型

1. NIO

  • 全称为Non-blocking I/ONew I/O (非阻塞输入/输出),是 Java 1.4 中引入的新 I/O 模型
  • 它与传统 I/O(Old I/O,即 BIO)有着本质的区别,主要用于高并发、高吞吐量的网络通信和文件操作
  • 有三大核心组件:Buffer、Channel、Selector

1.1. Buffer 缓冲区

传统 I/O是面向“流”的,数据像水流一样单向流动,无法前后移动

NIO是面向“缓冲区”的,数据被读取到一个缓冲区中,可以在缓冲区中前后移动(通过position,limit,capacity等指针控制)

常见缓冲区类型有:

  • ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer

  • ByteBuffer(最常用)

    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer

1.2. Channel 通道

传统 I/O 通过Stream读写数据,流是单向的(分为InputStreamOutputStream

NIO 的Channel是双向的,可以同时进行读和写操作;Channel 可以从 Buffer 中读取数据,也可以把数据写入 Buffer

常见的 Channel 有:

  • FileChannel(文件)、SocketChannel(TCP 客户端)、ServerSocketChannel(TCP 服务端)

1.3. Selector 选择器

Selector 被称为“多路复用器”,是 NIO 实现非阻塞的核心

它可以同时监听多个 Channel 的事件(如连接建立、数据到达、写入完成等)

通过 Selector,一个单独的线程就可以管理多个 Channel(成百上千个网络连接),极大地减少了线程上下文切换的开销

selector 版

thread

selector

channel

channel

channel

2. BIO

  • 全称为Blocking I/O(阻塞式输入/输出),是 Java 早期(JDK 1.4 之前)唯一的网络通信模型
  • 无论是客户端还是服务端,在进行网络读写操作时,如果数据没有准备好,线程就会被“卡住”(阻塞),直到数据就绪并完成操作

2.1. 工作模式

在 BIO 模型中,服务端的典型工作流程如下:

  1. ServerSocket 绑定端口:服务端创建ServerSocket,监听指定端口
  2. 阻塞等待连接:调用serverSocket.accept()方法,线程被阻塞,直到有新的客户端连接进来。一旦有连接,accept()返回一个Socket对象
  3. 阻塞等待数据:通过Socket获取输入流,调用inputStream.read()方法,线程再次被阻塞,直到客户端发送来数据并读取完毕
  4. 处理与响应:业务逻辑处理,然后通过输出流outputStream.write()将结果返回给客户端(写操作也可能阻塞)
  5. 循环往复:处理完一个请求后,继续调用accept()等待下一个连接

2.2. 线程模型

2.2.1. 一请求一线程

为了解决单线程无法并发的问题,传统的 BIO 采用了**“一请求一线程”**的模型:

  • 主线程专门负责调用accept()监听连接
  • 每当accept()返回一个Socket,就新建一个工作线程去处理这个Socket的读写和业务逻辑

多线程版

thread

socket1

thread

socket2

thread

socket3

缺点:

  • 内存占用高,每次都要新建线程
  • 如果并发很高,理论上会无限制的创建线程
  • 线程上下文切换成本高
  • 只适合连接数少的场景
2.2.2. 线程池

未解决方案一中线程资源频繁创建回收的问题,引入了线程池版方案

线程池版

thread

socket1

thread

socket2

socket3

socket4

缺点:

  • 阻塞模式下,线程仅能处理一个 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 的核心组件是FutureCompletionHandler(完成回调)

  1. Future 模型:发起操作后返回一个Future对象,你可以稍后调用future.get()阻塞等待结果(这其实退化为同步了,较少作为核心使用)
  2. CompletionHandler 回调模型:这是 AIO 的精髓。发起操作时传入一个回调对象,当 I/O 操作彻底完成(数据已拷贝到用户空间)时,系统自动调用回调对象中的方法

3.2. 工作模式

AIO 的网络通信和文件操作使用了不同的核心类:

  • 网络 AIOAsynchronousServerSocketChannel(服务端)、AsynchronousSocketChannel(客户端)
  • 文件 AIOAsynchronousFileChannel

AIO 服务端的典型工作流程:

  1. 创建通道并监听:打开AsynchronousServerSocketChannel,绑定端口
  2. 发起接受连接请求:调用channel.accept()此时不会阻塞。你需要传入一个CompletionHandler
  3. 系统接管:操作系统在底层监听连接。当有客户端连接时,操作系统完成连接建立,并自动触发你传入的CompletionHandlercompleted()方法
  4. 在回调中发起读操作:在completed()方法中,你拿到客户端的Channel,再次调用channel.read()读取数据,并再次传入一个新的CompletionHandler
  5. 系统再次接管:操作系统等待数据到达并拷贝到 Buffer 中,完成后触发读回调的completed()方法
  6. 处理与响应:在读回调中处理业务逻辑,并调用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 通常比较耗时,交给操作系统在后台异步完成,对主线程的影响最小

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 20:10:03

中国城市可再生能源数据集(2005-2021)

时间跨度2005-2021区域跨度中国327个地级市,4个直辖市地级市数据格式Excel形式数据简介中国城市可再生能源数据集,包含风电最终能耗、核点最终能耗、水电最终能耗、天然气最终消耗量、火电最终能耗、煤炭最终能耗、最终能耗、其他最终能耗、热量最终能耗…

作者头像 李华
网站建设 2026/6/13 19:59:09

音频文件太大怎么压缩?2026 年码率控制与方案对比

一段 3 分钟的 WAV 无损录音占用约 30MB、一个小时的播客节目未压缩可达 600MB、手机录制的会议音频发送到微信提示文件超过 100MB 限制——音频文件的体积问题在录音和内容创作场景中频繁出现。音频压缩不同于格式转换。格式转换只是在编码格式之间切换,音频压缩则…

作者头像 李华
网站建设 2026/6/13 19:58:13

上线只是一个产品的开始

最近,我独立开发并上线了一个微信小程序【发圈九宫格素材小铺】。 从需求梳理、页面设计、前后端开发,到服务器部署、提交审核,再到上线后的推广和运营,整个过程走下来,我最大的感受是: 开发只能决定一个产…

作者头像 李华
网站建设 2026/6/13 19:58:10

比付费App还好用!NAS一键部署电台中心,全球电台广播自由畅听!

比付费App还好用!NAS一键部署电台中心,全球电台广播自由畅听!哈喽小伙伴们好,我是Stark-C~貌似我们很多70、80、90的小伙伴多多少少都有一些“电台广播”情怀吧~。还记得小时候,我们很多时候的娱乐节目就是来自于那种带…

作者头像 李华
网站建设 2026/6/13 19:55:59

AI 驱动的 UI 组件智能组合推荐:从用户行为到布局方案的自动推导

AI 驱动的 UI 组件智能组合推荐:从用户行为到布局方案的自动推导 一、组件组合的"设计瓶颈":从需求到布局的经验依赖 前端开发中,将 UI 需求转化为组件组合方案是一个高度依赖经验的环节。一个"用户信息展示"的需求&…

作者头像 李华