| mmap 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。 |
| 这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数 |

| Linux 2.1 版本 提供了sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 SocketBuffer, |
| 同时,由于和用户态完全无关,就减少了一次上下文切换 |
| |
| 零拷贝从操作系统角度,是没有cpu 拷贝 |

| Linux 在 2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socketbuffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。 |
| |
| 这里其实有 一次cpu 拷贝kernel buffer -> socket buffer,但是,拷贝的信息很少,比如lenght , offset , 消耗低,可以忽略 |

| 1) 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据)。 |
| 2) 零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算 |
| 1) mmap 适合小数据量读写,sendFile 适合大文件传输。 |
| 2) mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。 |
| 3) sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)。 |
| # 服务端 |
| public class OldIOServer { |
| |
| public static void main(String[] args) throws Exception { |
| ServerSocket serverSocket = new ServerSocket(7001); |
| |
| while (true) { |
| Socket socket = serverSocket.accept(); |
| DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); |
| |
| try { |
| byte[] byteArray = new byte[4096]; |
| |
| while (true) { |
| int readCount = dataInputStream.read(byteArray, 0, byteArray.length); |
| |
| if (-1 == readCount) { |
| break; |
| } |
| } |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| } |
| } |
| } |
| } |
| |
| # 客户端 |
| public class OldIOClient { |
| |
| public static void main(String[] args) throws Exception { |
| Socket socket = new Socket("localhost", 7001); |
| |
| String fileName = "protoc-3.6.1-win32.zip"; |
| InputStream inputStream = new FileInputStream(fileName); |
| |
| DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); |
| |
| byte[] buffer = new byte[4096]; |
| long readCount; |
| long total = 0; |
| |
| long startTime = System.currentTimeMillis(); |
| |
| while ((readCount = inputStream.read(buffer)) >= 0) { |
| total += readCount; |
| dataOutputStream.write(buffer); |
| } |
| |
| System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime)); |
| |
| dataOutputStream.close(); |
| socket.close(); |
| inputStream.close(); |
| } |
| } |
- 使用NIO 零拷贝方式传递(transferTo)一个大文件
| # 服务端 |
| public class NewIOServer { |
| public static void main(String[] args) throws Exception { |
| |
| InetSocketAddress address = new InetSocketAddress(7001); |
| |
| ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); |
| |
| ServerSocket serverSocket = serverSocketChannel.socket(); |
| |
| serverSocket.bind(address); |
| |
| |
| ByteBuffer byteBuffer = ByteBuffer.allocate(4096); |
| |
| while (true) { |
| SocketChannel socketChannel = serverSocketChannel.accept(); |
| |
| int readcount = 0; |
| while (-1 != readcount) { |
| try { |
| |
| readcount = socketChannel.read(byteBuffer); |
| |
| }catch (Exception ex) { |
| |
| break; |
| } |
| |
| byteBuffer.rewind(); |
| } |
| } |
| } |
| } |
| |
| # 客户端 |
| public class NewIOClient { |
| public static void main(String[] args) throws Exception { |
| |
| SocketChannel socketChannel = SocketChannel.open(); |
| socketChannel.connect(new InetSocketAddress("localhost", 7001)); |
| String filename = "protoc-3.6.1-win32.zip"; |
| |
| |
| FileChannel fileChannel = new FileInputStream(filename).getChannel(); |
| |
| |
| long startTime = System.currentTimeMillis(); |
| |
| |
| |
| |
| |
| long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel); |
| |
| System.out.println("发送的总的字节数 =" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime)); |
| |
| |
| fileChannel.close(); |
| |
| } |
| } |
| 1) JDK 7 引入了 Asynchronous I/O,即 AIO。在进行 I/O 编程中,常用到两种模式: |
| Reactor和 Proactor。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的处理 |
| 2) AIO 即 NIO2.0,叫做异步不阻塞的 IO。AIO 引入异步通道的概念,采用了Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用 |
| 3) 目前 AIO 还没有广泛应用,Netty 也是基于NIO, 而不是AIO |
- BIO、NIO、AIO对比

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构