Java图文并茂详解NIO与零拷贝
作者:顽石九变
零拷贝是网络编程的关键,很多性能优化都离不开。在 Java 程序中,常用的零拷贝有 mmap(memory map,内存映射) 和 sendFile。那么它们在 OS(操作系统) 中,到底是怎么样的一个的设计?另外我们看下NIO 中如何使用零拷贝
零拷贝指的是没有CPU拷贝,并不是不拷贝;减少上下文切换
一、概念说明
1、传统IO
需要4次拷贝,3次上下文切换
2、mmap
mmap 通过内存映射,将文件映射到内存缓冲区,同时用户空间可以共享内存缓冲区的数据,减少内核空间到用户空间的拷贝
需要3次拷贝,3次上下文切换
3、sendfile
Linux 2.4 避免了从内核缓冲区到Socket Buffer的拷贝,直接拷贝到协议栈,从而减少一次数据拷贝
需要2次拷贝,3次上下文切换
4、mmap与sendfile
mmap适合小数据量读写,sendfile适合大文件传输
mmap需要4次上下文切换,3次数据拷贝;sendfile需要3次上下文切换,最少2次数据拷贝
send可用利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)
二、传统IO传输文件代码示例
1、服务端代码
import java.io.DataInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class BIOServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(7000); while (true) { Socket socket = serverSocket.accept(); DataInputStream dataInputStream = new DataInputStream(socket.getInputStream()); try { long total = 0; byte[] bytes = new byte[4096]; FileOutputStream fileOutputStream = new FileOutputStream("d:\\temp\\04.zip"); while (true) { int read = dataInputStream.read(bytes, 0, bytes.length); if (read == -1) { //文件读取结束,退出循环 break; } total += read; fileOutputStream.write(bytes, 0, read); } System.out.println("收到客户端发送文件,总字节数:" + total); fileOutputStream.close(); } catch (Exception e) { e.printStackTrace(); } } } }
2、客户端代码
import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.net.Socket; public class BIOClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 7000); FileInputStream fileInputStream = new FileInputStream("d:\\temp\\03.zip"); DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); byte[] bytes = new byte[4096]; long readCount; long total = 0; long start = System.currentTimeMillis(); while ((readCount = fileInputStream.read(bytes)) >= 0) { total += readCount; dataOutputStream.write(bytes); } System.out.println("发送总字节数:" + total + ", 总耗时:" + (System.currentTimeMillis() - start)); dataOutputStream.close(); socket.close(); fileInputStream.close(); } }
3、控制台出输出
测试发送9M的压缩文件,耗时在26ms左右
发送总字节数:9428963, 总耗时:26
三、NIO传输文件代码示例
1、服务端代码
package com.hj.io.nio.zero; import java.io.FileOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; public class NIOServerFile { public static void main(String[] args) throws IOException { InetSocketAddress address = new InetSocketAddress(7000); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(address); //创建buffer ByteBuffer byteBuffer = ByteBuffer.allocate(4096); while (true) { //等待客户端链接 SocketChannel socketChannel = serverSocketChannel.accept(); System.out.println("收到客户端链接"); int total = 0; FileOutputStream fileOutputStream = new FileOutputStream("d:\\temp\\05.zip"); //循环读取数据,并存储到硬盘 while (true) { try { int readCount = socketChannel.read(byteBuffer); if (readCount == -1) { //文件读取结束 break; } total += readCount; fileOutputStream.write(byteBuffer.array(),0,readCount); //将buffer倒带 byteBuffer.rewind(); } catch (IOException e) { break; } } System.out.println("收到客户端发送文件,总字节数:" + total); fileOutputStream.close(); } } }
2、客户端代码
import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; public class NIOClientFile { public static void main(String[] args) throws IOException { //打开一个SocketChannel并链接到服务器端 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 7000)); //打开一个文件 FileChannel fileChannel = new FileInputStream("d:\\temp\\03.zip").getChannel(); //发送文件到服务器 long start = System.currentTimeMillis(); //在linux下,一次transferTo调用就可以完成传输 //在windows下,一次transferTo调用最多只能传8M,大文件需要分段传输,需要注意传输位置 //transferTo底层使用零拷贝 long total = fileChannel.size(); long sended = 0; while (sended < total) { //从上一次传输位置继续发送 long lenth = fileChannel.transferTo(sended, fileChannel.size(), socketChannel); sended += lenth; } System.out.println("发送总字节数:" + sended + ",总耗时:" + (System.currentTimeMillis() - start)); //关闭channel fileChannel.close(); socketChannel.close(); } }
3、控制台出输出
测试发送9M的压缩文件,耗时在16ms左右
发送总字节数:9428963,总耗时:16
四、总结
使用零拷贝传输,性能明显高于传统IO传输
到此这篇关于Java图文并茂详解NIO与零拷贝的文章就介绍到这了,更多相关Java NIO与零拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!