業務場景:將磁盤上的文件讀取到內存裏面發送給用戶 通過網絡 (Linux,Unix)
我們分析一下普通的IO拷貝方式,從操作系統視角來看 .從內核空間的拷貝過程:
我們再來看看NIO零拷貝的內存方式
但是我們還是看到了兩次的copy操作, 那麼是否能直接把我們的數據拷貝到 socket緩存區呢?也就是這樣的
** 操作系統提供的支持 ,有些系統需要是支持SocketBuffer直接探測到文件緩存區的那樣,就直接進行了Socket與文件Buffer之間的拷貝**
0拷貝 ,問題一,
既然拷貝是在內核空間執行的那麼用戶需要參與怎麼辦,
(內存映射,將內存的地址 保存到用戶空間 當中 ,可以直接訪問, 減少不必要的內存空間)
0拷貝,問題二:
意味着什麼? 看操作系統是否支持
內存映射不一定是性能最好的,同時也要通過內存測試等等 (操作的系統的虛擬內存管理等相關知識)
問題三
scatter/gather 是什麼意思 參照我的令一篇 進行深度理解
代碼實現 我們先寫一個普通IO的操作也就是一個Server 一個Client 然後 試着往SocketBuffer寫一個大一點的東西
IOServer端
package com.shensiyuan.zerocopy;
import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class OldIOServer {
public static void main(String[] args) throws Exception {
//綁定端口號
ServerSocket serverSocket = new ServerSocket(8899);
//死循環等待鏈接
while (true) {
//監聽鏈接並且接受鏈接
Socket socket = serverSocket.accept();
//建立連接 (文件的連接)
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
try {
//定義緩存區
byte[] byteArray = new byte[4096];
while (true) {
//不斷的獲取到流當中的數據
int readCount = dataInputStream.read(byteArray, 0, byteArray.length);
if (-1 == readCount) {
break;
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
IOClient端
public class OldIOClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 8899);
String fileName = "D:/【文件夾】Spring boot源碼解析/Spring boot源碼解析/第3節SpringApplication初始化.avi";
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));
dataOutputStream.close();
socket.close();
inputStream.close();
}
}
NIOServer端
package com.shensiyuan.zerocopy;
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 Exception {
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();
//設置阻塞 如果有Selector那麼設置成false
socketChannel.configureBlocking(true);
int readCount = 0;
//數據不斷的進來
while (-1 != readCount) {
try {
readCount = socketChannel.read(byteBuffer);
} catch (Exception ex) {
ex.printStackTrace();
}
//翻轉讀寫轉換 flip
//但是我們只進行了些,所以用rewind()方法 將Postion重放
byteBuffer.rewind();
}
}
}
}
NIOClient端
package com.shensiyuan.zerocopy;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
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 = "D:/【文件夾】Spring boot源碼解析/Spring boot源碼解析/第3節SpringApplication初始化.avi";
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();
}
}
Result對比
IO 發送總字節數: 228547488, 耗時: 7466
NIO 發送總字節數: 228547488, 耗時: 2617
我們看到 用NIO快了許多
我們看到在NIO 方法中 fileChannel.transferTo(0, fileChannel.size(), socketChannel);方法起到了關鍵的作用
JAVADOC
<p> This method is potentially much more efficient than a simple loop
* that reads from this channel and writes to the target channel. Many
* operating systems can transfer bytes directly from the filesystem cache
* to the target channel without actually copying them. </p>