Java图文并茂详解NIO与零拷贝
顽石九变 人气:0零拷贝指的是没有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传输
加载全部内容