1.本文总结自B站《netty-尚硅谷》,很不错;
2.本文部分内容参考自 NIO效率高的原理之零拷贝与直接内存映射 - 腾讯云开发者社区-腾讯云

【图解】
小结: 上述过程中,操作系统底层 有4次用户态与内核态的切换,2次cpu拷贝(DMA拷贝不占CPU,不计入);文件读写(拷贝)性能较低;
补充:
1)零拷贝 :
2)零拷贝流程图 :

【图解】
【小结】
NIO零拷贝适用于以下场景:
注意:字节缓冲 4M (字节缓冲大小会影响传输性能,当然了,一定条件下,缓冲越大越好);
1)服务器:
- /**
- * @Description 传统IO服务器
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月20日
- */
- public class OldIOServer {
-
- public static void main(String[] args) throws IOException {
- // 服务器监听端口 7001
- ServerSocket serverSocket = new ServerSocket(7001);
-
- while (true) {
- // 阻塞式等待客户端请求链接
- Socket socket = serverSocket.accept();
- DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
- try {
- byte[] byteArr = new byte[4096];
- // 读取客户端的数据到字节数组
- while (dataInputStream.read(byteArr, 0, byteArr.length) != -1) ;
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
2)客户端:
- /**
- * @Description 传统IO客户端
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月20日
- */
- public class OldIOClient {
-
- public static void main(String[] args) throws IOException {
- Socket socket = new Socket("127.0.0.1", 7001);
- // 传输 一个 zip文件
- InputStream inputStream = new FileInputStream("D:\\cmb\\studynote\\netty\\temp\\springboot.zip");
-
- 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);
- }
- long cost = System.currentTimeMillis() - startTime;
- System.out.println("发送总字节数 " + total + ", 耗时 = " + cost);
- // 关闭资源
- dataOutputStream.close();
- socket.close();
- inputStream.close();
- }
- }
3)效果:
发送总字节数 4491230, 耗时 = 50
注意:字节缓冲 4M (字节缓冲大小会影响传输性能,当然了,一定条件下,缓冲越大越好);
1)服务器:
- /**
- * @Description nio实现零拷贝服务器
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月20日
- */
- public class ZeroCopyNIOServer {
- public static void main(String[] args) throws IOException {
- // 服务器套接字通道
- ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- // 绑定端口
- serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 7001));
-
- ByteBuffer buffer = ByteBuffer.allocate(4096);
- while (true) {
- // 等待客户端连接
- SocketChannel socketChannel = serverSocketChannel.accept();
- // 读取数据
- while (socketChannel.read(buffer) != -1) {
- // 缓冲倒带, 设置 position=0, mark作废
- buffer.rewind();
- }
- }
-
- }
- }
2)客户端:
- /**
- * @Description nio实现零拷贝服务器
- * @author xiao tang
- * @version 1.0.0
- * @createTime 2022年08月20日
- */
- public class ZeroCopyNIOClient {
- public static void main(String[] args) throws IOException {
- SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 7001));
- FileChannel fileChannel = new FileInputStream("D:\\cmb\\studynote\\netty\\temp\\springboot.zip").getChannel();
- long startTime = System.currentTimeMillis();
-
- // 在 linux 下,一次调用 transferTo 方法就可以完成传输
- // 在 window下,一次调用 transferTo 只能发送 8M,如果大于8M,则需要分段传输文件
- // transferTo 底层就用到了 零拷贝
- long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
- long cost = System.currentTimeMillis() - startTime;
- System.out.println("客户端发送数据成功,耗时= " + cost + ", 传输的字节总数 = " + transferCount);
- }
- }
3)效果:
客户端发送数据成功,耗时= 10, 传输的字节总数 = 4491230
4)补充: 关于 FileChannel.transferTo 方法
5)transferTo方法底层使用的是 sendfile 系统调用(零拷贝)
【表】传统IO与NIO零拷贝传输性能对比 (文件大小 4M)
| Java IO类型 | 传输耗时 | 备注 |
| 传统IO | 50ms | |
| NIO零拷贝 | 10ms | 性能更优 |