您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關NIO與零拷貝的示例分析,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
先來看如下一段代碼:
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(8888).accept();
socket.getOutputStream().write(arr);
這段代碼就是讀取一個文件,然后再把它寫出去,看起來就幾行代碼,其實涉及到多次拷貝,其流程如下:
讀取需要拷貝的數據,這個過程有兩個步驟:首先將文件通過DMA(direct memory access,直接內存拷貝,不經過CPU) 拷貝到系統內核的buffer中,然后在內核buffer中通過 CPU拷貝到用戶的buffer中,這才完成了讀取文件的步驟。即前四行就發生了兩次拷貝。
寫數據的時候,將數據從用戶buffer通過CPU拷貝到socket buffer中,最后從socket buffer通過DMA拷貝到協議棧。即最后一行也發生了兩次拷貝。
整個過程,發生了四次拷貝,三次狀態的切換。從一開始的用戶態,切換到內核態,再切換到用戶態,最后再切換成內核態。一次簡單的讀寫,就有這么多名堂,性能肯定是不好的,所有就出現了零拷貝,零拷貝,不是不拷貝,而是整個過程不需要進行CPU拷貝。
1、使用mmap優化上述流程:mmap,是指通過內存映射,將文件映射到內核緩沖區,同時,用戶空間可以共享內核空間的數據,這樣,在進行網絡傳輸時,就可以減少內核空間到用戶空間的拷貝次數。同樣做上面的事情,使用mmap時整個過程如下:
首先通過DMA拷貝將硬盤數據拷貝到內核buffer,但是因為用戶buffer可以共享內核buffer的數據,所以步驟二的cpu拷貝就免了;
然后是直接從內核buffer通過CPU拷貝到socket buffer,最后DMA拷貝到協議棧。
整個過程三次拷貝,三次狀態的切換,相比傳統拷貝,優化了一丟丟,但這并不是零拷貝。
2、使用sendFile優化:linux 2.1的sendFile:sendFile是linux2.1版本開始提供的一個函數,可以讓文件直接從內核buffer進入到socket buffer,不需要經過用戶態,過程如下:
整個過程還是3次拷貝,但是減少了一次裝態切換,從用戶態到內核態再到用戶態,只經過了兩次切換。這里還是有一次CPU拷貝,還不是真正的零拷貝。
linux 2.4的sendFile:linux 2.4對sendFile又做了一些優化,首先還是DMA拷貝到內核buffer,然后再通過CPU拷貝到socket buffer,最后DMA拷貝到協議棧。優化的點就在于,這次的CPU拷貝,拷貝的內容很少,只拷貝內核buffer的長度、偏移量等信息,消耗很低,可以忽略。因此,這個就是零拷貝。NIO的transferTo
方法就可以實現零拷貝。
1、傳統IO拷貝大文件:
public class OldIoServer {
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
Socket socket = serverSocket.accept();
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
byte[] byteArray = new byte[4096];
while (true) {
int readCount = dataInputStream.read(byteArray, 0, byteArray.length);
if (-1 == readCount) {
break;
}
}
}
}
}
public class OldIoClient {
@SuppressWarnings("resource")
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 6666);
// 需要拷貝的文件
String fileName = "E:\\download\\soft\\windows\\jdk-8u171-windows-x64.exe";
InputStream inputStream = new FileInputStream(fileName);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
long readCount;
long total = 0;
long start = System.currentTimeMillis();
while ((readCount = inputStream.read(buffer)) >= 0) {
total += readCount;
dataOutputStream.write(buffer);
}
long end = System.currentTimeMillis();
System.out.println("傳輸總字節數:" + total + ",耗時:" + (end - start) + "毫秒");
dataOutputStream.close();
inputStream.close();
socket.close();
}
}
這里拷貝了一個JDK,最后運行結果如下:
傳輸總字節數:217342912,耗時:4803毫秒
可以看到,將近5秒鐘。接下來看看使用NIO的transferTo
方法耗時情況:
public class NioServer {
public static void main(String[] args) throws IOException {
InetSocketAddress address = new InetSocketAddress(6666);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(address);
ByteBuffer buffer = ByteBuffer.allocate(4096);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
int readCount = 0;
while (-1 != readCount) {
readCount = socketChannel.read(buffer);
buffer.rewind(); // 倒帶,將position設置為0,mark設置為-1
}
}
}
}
public class NioClient {
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 6666));
String fileName = "E:\\download\\soft\\windows\\jdk-8u171-windows-x64.exe";
FileChannel channel = new FileInputStream(fileName).getChannel();
long start = System.currentTimeMillis();
// 在linux下,transferTo方法可以一次性發送數據
// 在windows中,transferTo方法傳輸的文件超過8M得分段
long totalSize = channel.size();
long transferTotal = 0;
long position = 0;
long count = 8 * 1024 * 1024;
if (totalSize > count) {
BigDecimal totalCount = new BigDecimal(totalSize).divide(new BigDecimal(count)).setScale(0, RoundingMode.UP);
for (int i=1; i<=totalCount.intValue(); i++) {
if (i == totalCount.intValue()) {
transferTotal += channel.transferTo(position, totalSize, socketChannel);
} else {
transferTotal += channel.transferTo(position, count + position, socketChannel);
position = position + count;
}
}
} else {
transferTotal += channel.transferTo(position, totalSize, socketChannel);
}
long end = System.currentTimeMillis();
System.out.println("發送的總字節:" + transferTotal + ",耗時:" + (end - start) + "毫秒");
channel.close();
socketChannel.close();
}
}
客戶端發送文件調用transferTo
方法要注意,在window中,這個方法一次只能傳輸8M,超過8M的文件要分段,像代碼中那樣分段傳輸,在linux中是沒這個限制的。運行后結果如下:
發送的總字節:217342912,耗時:415毫秒
從結果可以看到,BIO與NIO耗時相差一個數量級,NIO只要0.4s,而BIO要4s。所以在網絡傳輸中,使用NIO的零拷貝,可以大大提高性能。
上述就是小編為大家分享的NIO與零拷貝的示例分析了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。