NIO 零拷贝
OIO在读取文件传输的时候,在操作系统中发生了多次上下文的切换和多次的数据拷贝。
BIO在读取文件的流程图

NIO零拷贝读取文件的流程图,实际上这个也有一次拷贝,就是从kernel内核空间拷贝到socket buffer中。

下面再写个零拷贝的例子深刻理解一下,首先是OIO的方式。
OldServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import java.io.DataInputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket;
public class OldServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8899);
while (true) { Socket socket = serverSocket.accept(); DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
try { byte[] byteArry = new byte[4096];
while (true) { int readCount = dataInputStream.read(byteArry, 0, byteArry.length);
if (-1 == readCount) { break; }
} } catch (Exception ex) { ex.printStackTrace(); } } } }
|
OldClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket;
public class OldClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("localhost", 8899);
String fileName = "/Users/xiaomai/software/工具/CleanMyMac 3.5.1.dmg";
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));
inputStream.close(); dataOutputStream.close(); } }
|
结果:
下面是NIO的方式:
NewIOServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel;
public class NewIOServer { public static void main(String[] args) throws IOException { InetSocketAddress address = new InetSocketAddress(8899);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ServerSocket serverSocket = serverSocketChannel.socket(); serverSocket.setReuseAddress(true); serverSocket.bind(address);
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
while (true) { SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(true);
int readCount = 0;
while (-1 != readCount) { try { readCount = socketChannel.read(byteBuffer); } catch (Exception ex) { ex.printStackTrace(); }
byteBuffer.rewind(); }
}
} }
|
NewIOClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public class NewIOClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("localhost", 8899)); socketChannel.configureBlocking(true);
String fileName = "/Users/xiaomai/software/工具/CleanMyMac 3.5.1.dmg";
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(); } }
|
输出:
通过简单的测试我们也可以发现,NIO方式的速度确实快了很多。
从Linux2.4开始,还提供了scatter/gather的方式使速度更上一层,实现了真正意义上的零拷贝。

将文件拷贝到kernel缓冲区后,只将地址和长度等必要信息拷贝到socket buffer中,等到要发送文件的时候,从socket buffer中读取文件长度和地址,从kernel中读取真正的文件,这是一种gather操作,然后把数据直接发送到了服务器端,跟之前的对比,省去了从kernel内核空间拷贝到socket buffer的过程。
