Java IO知识梳理作者没有四次元口袋的蓝胖日期2026-07-11标签Java, IO, NIO一、传统 IO1.1 IO 流分类Java IO 流按数据类型分为字节流处理字节数据8位适合二进制文件图片、视频等字符流处理字符数据16位适合文本文件按数据流向分为输入流从外部读取数据到程序输出流从程序写出数据到外部类型输入流输出流字节流InputStreamOutputStream字符流ReaderWriter1.2 核心类字节流// 文件字节流FileInputStreamfisnewFileInputStream(a.txt);FileOutputStreamfosnewFileOutputStream(b.txt);// 缓冲流提高性能BufferedInputStreambisnewBufferedInputStream(fis);BufferedOutputStreambosnewBufferedOutputStream(fos);// 对象流序列化ObjectInputStreamoisnewObjectInputStream(fis);ObjectOutputStreamoosnewObjectOutputStream(fos);字符流// 文件字符流FileReaderfrnewFileReader(a.txt);FileWriterfwnewFileWriter(b.txt);// 缓冲流BufferedReaderbrnewBufferedReader(fr);BufferedWriterbwnewBufferedWriter(fw);// 转换流字节流 ↔ 字符流InputStreamReaderisrnewInputStreamReader(fis,UTF-8);OutputStreamWriteroswnewOutputStreamWriter(fos,UTF-8);1.3 典型用法// 1. 复制文件字节流try(FileInputStreamfisnewFileInputStream(source.jpg);FileOutputStreamfosnewFileOutputStream(target.jpg)){byte[]buffernewbyte[1024];intlen;while((lenfis.read(buffer))!-1){fos.write(buffer,0,len);}}// 2. 读取文本字符流 缓冲try(BufferedReaderbrnewBufferedReader(newFileReader(a.txt))){Stringline;while((linebr.readLine())!null){System.out.println(line);}}1.4 传统 IO 的问题面向流一次只能读一个字节或字符效率低阻塞式read() 会阻塞线程直到数据准备好单连接一个连接只能处理一个客户端线程模型每个连接需要一个线程BIO 模型二、NIONew IOJava NIONew IOJava 1.4 引入是非阻塞 IO核心三大组件Channel、Buffer、Selector。2.1 Channel通道Channel 是双向的既可读也可写类似管道。核心实现FileChannel// 文件通道不支持非阻塞SocketChannel// TCP 客户端通道ServerSocketChannel// TCP 服务端通道DatagramChannel// UDP 通道FileChannel 示例// 写入文件try(FileChannelchannelnewFileOutputStream(a.txt).getChannel()){ByteBufferbufferByteBuffer.wrap(Hello NIO.getBytes());channel.write(buffer);}// 读取文件try(FileChannelchannelnewFileInputStream(a.txt).getChannel()){ByteBufferbufferByteBuffer.allocate(1024);channel.read(buffer);buffer.flip();// 切换为读模式System.out.println(StandardCharsets.UTF_8.decode(buffer));}// 文件复制零拷贝try(FileChannelsrcnewFileInputStream(source.txt).getChannel();FileChanneldestnewFileOutputStream(target.txt).getChannel()){src.transferTo(0,src.size(),dest);// 零拷贝效率最高}2.2 Buffer缓冲区Buffer 是一个容器所有数据都必须通过 Buffer 读写。核心属性intcapacity;// 容量创建后不可变intposition;// 当前读写位置intlimit;// 限制最多能读/写多少intmark;// 标记用于 reset状态转换分配 Buffercapacity10, position0, limit10 写入数据后position5, limit10 调用 flip()position0, limit5准备读取 读取后调用 clear()position0, limit10准备重新写入常用方法ByteBufferbufferByteBuffer.allocate(1024);// 分配 1024 字节// 写入buffer.put(Hello.getBytes());// 切换读模式buffer.flip();// 读取byte[]datanewbyte[buffer.remaining()];buffer.get(data);// 重置重新读取buffer.rewind();// position 0// 清空准备重新写入buffer.clear();// position 0, limit capacity2.3 Selector选择器Selector 是 NIO 的核心实现多路复用一个线程可以管理多个 Channel。工作原理Selector ├── 监听多个 Channel ├── 检查 Channel 是否就绪读/写/连接/接受 └── 就绪的 Channel 交给线程处理典型用法// 创建 SelectorSelectorselectorSelector.open();// 注册 Channel必须是非阻塞模式ServerSocketChannelserverChannelServerSocketChannel.open();serverChannel.configureBlocking(false);// 设置为非阻塞serverChannel.bind(newInetSocketAddress(8080));serverChannel.register(selector,SelectionKey.OP_ACCEPT);// 监听接受连接// 轮询检查while(true){selector.select();// 阻塞直到有 Channel 就绪SetSelectionKeykeysselector.selectedKeys();IteratorSelectionKeyiteratorkeys.iterator();while(iterator.hasNext()){SelectionKeykeyiterator.next();if(key.isAcceptable()){// 接受连接SocketChannelclientserverChannel.accept();client.configureBlocking(false);client.register(selector,SelectionKey.OP_READ);}elseif(key.isReadable()){// 读取数据SocketChannelclient(SocketChannel)key.channel();ByteBufferbufferByteBuffer.allocate(1024);client.read(buffer);}iterator.remove();// 移除已处理的 key}}2.4 零拷贝Zero Copy传统 IO磁盘 → 内核缓冲区 → 用户缓冲区 → Socket缓冲区 → 网卡 4次拷贝4次上下文切换NIO 零拷贝磁盘 → 内核缓冲区 → 网卡 2次拷贝0次用户态拷贝实现方式// FileChannel.transferTo()srcChannel.transferTo(position,count,destChannel);// FileChannel.transferFrom()destChannel.transferFrom(srcChannel,position,count);三、IO vs NIO 对比对比维度传统 IOBIONIO数据模型面向流Stream面向缓冲区Buffer阻塞方式阻塞式非阻塞式线程模型一连接一线程多路复用Selector通道单向输入/输出双向Channel选择器无有Selector性能较低阻塞等待较高非阻塞多路复用适用场景连接数少、短连接连接数多、长连接聊天、游戏编程复杂度简单复杂零拷贝不支持支持transferTo3.1 线程模型对比BIO一连接一线程客户端1 → 线程1 客户端2 → 线程2 客户端3 → 线程3 ... 客户端100 → 线程100100个线程资源消耗大NIO多路复用客户端1 ─┐ 客户端2 ─┤ 客户端3 ─┼→ Selector → 线程池少量线程处理所有连接 ... │ 客户端100 ┘3.2 什么时候选谁选 BIO连接数少 100短连接HTTP 请求编程简单优先选 NIO连接数多 1000长连接WebSocket、游戏服务器高性能要求需要零拷贝四、Netty 简介Netty 是基于 NIO 的高性能网络框架封装了 NIO 的复杂性。核心优势统一的 API支持多种协议零拷贝高度可定制的线程模型高性能比原生 NIO 高成熟稳定Dubbo、RocketMQ、Elasticsearch 都用典型应用RPC 框架Dubbo消息队列RocketMQ、Kafka搜索引擎Elasticsearch游戏服务器五、常见陷阱5.1 Buffer 忘记 flip()ByteBufferbufferByteBuffer.allocate(1024);buffer.put(Hello.getBytes());// 写入// 忘记 flip()channel.read(buffer);// 读不到数据position 还在末尾5.2 Channel 忘记设置非阻塞SocketChannelchannelSocketChannel.open();// 忘记设置非阻塞channel.configureBlocking(false);// 必须设置channel.register(selector,SelectionKey.OP_READ);5.3 Selector 忘记 remove()while(iterator.hasNext()){SelectionKeykeyiterator.next();// 处理 keyiterator.remove();// 必须移除否则会重复处理}六、思维导图速览┌───────────────────────────────────────────────────┐ │ Java IO vs NIO │ ├───────────────────────────────────────────────────┤ │ │ │ 传统 IOBIO │ │ • 面向流InputStream/OutputStream │ │ • 阻塞式read 会阻塞 │ │ • 一连接一线程 │ │ • 适合连接数少、短连接 │ │ │ │ NIONew IO │ │ • 面向缓冲区Buffer │ │ • 非阻塞式 │ │ • 三大组件Channel/Buffer/Selector │ │ • 多路复用一个线程管理多个 Channel │ │ • 零拷贝transferTo/transferFrom │ │ • 适合连接数多、长连接、高性能 │ │ │ │ Buffer 状态转换 │ │ 分配 → 写入 → flip() → 读取 → clear() → 循环 │ │ │ │ 核心对比 │ │ 数据模型流 vs 缓冲区 │ │ 阻塞方式阻塞 vs 非阻塞 │ │ 线程模型一连接一线程 vs 多路复用 │ │ 通道单向 vs 双向 │ │ 性能较低 vs 较高 │ │ │ │ Netty │ │ • 基于 NIO 的高性能框架 │ │ • 封装复杂度提供统一 API │ │ • 应用Dubbo/RocketMQ/ES │ └───────────────────────────────────────────────────┘七、写在最后IO/NIO 的区别是面试高频题要能从数据模型、阻塞方式、线程模型三个维度对比Buffer 的 flip()是常见陷阱一定要理解 position/limit/capacity 的状态转换Selector 多路复用是 NIO 的核心理解一个线程管理多个连接的原理零拷贝是高级考点知道 transferTo 的实现原理