IO杂记I

发布时间:2026/5/19 14:44:06

IO杂记I IO 杂记一、Selector 与 select()selector.select()不会创建新线程而是让当前线程阻塞等待直到有 I/O 事件就绪。比喻一个人站在门口不来客人就不动。selector.selectNow()是非阻塞版本瞥一眼门口没人就立刻返回。Selector 底层会调用操作系统的多路复用 API如 Linux 的epoll_wait而非自己轮询。二、I/O 复用I/O Multiplexing核心思想用一个线程 一个多路复用器Selector同时监控多个通道Socket的 I/O 事件。典型步骤Selector.open()创建多路复用器。serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT)注册服务端通道关注ACCEPT事件。selector.select()阻塞等待就绪事件底层调用epoll_wait/select/kqueue。遍历selectedKeys集合根据事件类型isAcceptable、isReadable、isWritable分别处理。三、三种 I/O 模型对比模型线程模型特点BIO阻塞 I/O一连接一线程read()/write()阻塞线程多连接需多线程或线程池NIOI/O 复用单线程管理成千上万连接通过 Selector 非阻塞通道轮询就绪事件AIO异步 I/O少量线程如 CPU 核心数 回调发起读写立即返回OS 完成数据拷贝后回调CompletionHandler四、零拷贝Zero-Copy1. 核心概念CPU 拷贝CPU 逐字节搬运数据占用 CPU 时间。DMA 拷贝DMA 控制器独立搬运数据不占用 CPU。2. 传统readwrite流程FileInputStreamfisnewFileInputStream(file.txt);Socketsocket...;byte[]buffernewbyte[1024];intlenfis.read(buffer);socket.getOutputStream().write(buffer,0,len);底层步骤DMA 拷贝磁盘 → 内核缓冲区PageCacheCPU 拷贝内核缓冲区 → 用户缓冲区byte[]CPU 拷贝用户缓冲区 → Socket 内核缓冲区DMA 拷贝Socket 内核缓冲区 → 网卡CPU 拷贝次数2DMA 拷贝次数2系统调用次数2readwrite上下文切换次数43.mmapwriteFileChannelfileChannelFileChannel.open(Paths.get(file.txt));MappedByteBuffermappedBufferfileChannel.map(MapMode.READ_ONLY,0,fileSize);socketChannel.write(mappedBuffer);原理mmap将磁盘文件映射到进程地址空间用户直接访问内核缓冲区省去“内核→用户”的 CPU 拷贝。步骤DMA 拷贝磁盘 → 内核缓冲区PageCacheCPU 拷贝内核缓冲区 → Socket 内核缓冲区仍有一次DMA 拷贝Socket 内核缓冲区 → 网卡CPU 拷贝次数1DMA 拷贝次数2系统调用次数2mmapwrite上下文切换次数44.sendfile基础sendfile是专门在两个文件描述符之间直接传输数据的系统调用无用户空间拷贝。流程DMA 拷贝磁盘 → 内核缓冲区CPU 拷贝内核缓冲区 → Socket 缓冲区DMA 拷贝Socket 缓冲区 → 网卡CPU 拷贝次数1DMA 拷贝次数2系统调用次数1上下文切换次数25.sendfile DMA Gather若网卡支持Scatter/Gather DMA则 CPU 无需再将数据从内核文件缓冲区复制到 Socket 缓冲区网卡直接从 PageCache 中抓取分散的数据。CPU 拷贝次数0真正的 CPU 零拷贝DMA 拷贝次数2系统调用次数1上下文切换次数26. 对比总表方式CPU 拷贝DMA 拷贝系统调用上下文切换传统 readwrite2224mmapwrite1224sendfile基础1212sendfile DMA gather copy02127. DMA Gather 详解普通 DMA只能传输连续内存块若数据分散需 CPU 先复制到连续缓冲区。DMA GatherDMA 控制器维护描述符列表地址长度直接从不连续的多个内存区域抓取数据组合后发送给设备。比喻普通 DMA快递员必须从整箱取货 → 你先将散货装进箱子CPU 拷贝DMA Gather快递员拿着清单上门自取 → 你无需动手8. Java 中的transferToFileChannel.transferTo()底层会优先使用零拷贝如 Linux 的sendfile条件不满足时优雅降级为传统拷贝。实现逻辑伪代码if(满足零拷贝条件){if(sendfile(...)0||transmitFile(...)0)returnbytes_transferred;}returntransferToViaTemporaryBuffer(src,target);// 用户态兜底内部检查源通道是否为普通本地文件目标通道是否为SocketChannel或同一文件系统上的FileChannel操作系统是否支持零拷贝系统调用局限性零拷贝失效场景目标为普通FileChannel且与源不在同一个文件系统如跨分区 C: → D:Windows 上transferTo不是真正的零拷贝数据需 SSL/TLS 加密部分平台如 macOS对FileChannel→FileChannel不支持零拷贝最佳实践分段传输单次不超过 2GB检查返回值循环传输使用 try-with-resources 关闭通道加密场景避免使用零拷贝五、零拷贝的目标总结减少/消除 CPU 拷贝减少上下文切换次数两次 DMA 拷贝磁盘→内核、内核→网卡无法避免因为它们由硬件高效完成

相关新闻