文章目录
什么是零拷贝
传统Linux操作系统的标准IO接口基于数据拷贝,虽然不需要进行实际物理磁盘的IO操作,但是传输过程中的数据拷贝操作会极大消耗CPU,造成操作系统的有效数据传输操作能力,零拷贝在数据拷贝同时允许CPU执行其他的任务
在java程序中常用的零拷贝有mmap(内存映射)和sendFile
传统IO模型
File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);
DMA:direct memory access 直接内存拷贝(不使用 CPU)
mmap 优化
通过内存映射,将文件映射到内核缓冲区,同时用户空间可以共享内核空间的数据,这样在进行网络传输时,减少内核空间到用户空间的拷贝次数
sendFile优化
数据根本不经过用户态.直接从内核缓冲区进入到SocketBuffer,同时由于和用户态完全无关,就减少了一次上下文切换
这里其实有一次cpu 拷贝 kernel buffer -> socket buffer,但是开吧数据很少,基本是length,offset,消耗低
零拷贝是从操作系统角度来说,因为内核缓冲区之间没有数据重复(只有kernel buffer有一份数据),可以减少数据复制,更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算
mmap和sendFile区别
mmap适合小数据量读写,sendFile适合大文件传输
mmap上下文切换4次,sendFile 三次上下文切换,最少2次数据拷贝
sendFile可以利用DMA方式,减少cpu拷贝,mmap则不能
案例 传统IO与NIO传输文件时间对比
server
package com.zyd.nio;
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(8888);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(address);
ByteBuffer allocate = ByteBuffer.allocate(4096);
while (true) {
SocketChannel socket = serverSocketChannel.accept();
int readCount = 0;
while (-1 != readCount){
try {
readCount = socket.read(allocate);
}catch (Exception e){
break;
}
}
allocate.rewind();
}
}
}
client
package com.zyd.nio;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
public class NewIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost",8888));
String file = "";
//得到一个文件channel
FileChannel channel = new FileInputStream(file).getChannel();
//准备发送
long startTime = System.currentTimeMillis();
long transferTo = channel.transferTo(0, channel.size(), socketChannel);
System.out.println("发送的总字节数 = " + transferTo + "耗时: "+(System.currentTimeMillis() - startTime));
}
}