Java圖文并茂詳解NIO與零拷貝
零拷貝指的是沒(méi)有CPU拷貝,并不是不拷貝;減少上下文切換
一、概念說(shuō)明
1、傳統(tǒng)IO
需要4次拷貝,3次上下文切換

2、mmap
mmap 通過(guò)內(nèi)存映射,將文件映射到內(nèi)存緩沖區(qū),同時(shí)用戶空間可以共享內(nèi)存緩沖區(qū)的數(shù)據(jù),減少內(nèi)核空間到用戶空間的拷貝
需要3次拷貝,3次上下文切換

3、sendfile
Linux 2.4 避免了從內(nèi)核緩沖區(qū)到Socket Buffer的拷貝,直接拷貝到協(xié)議棧,從而減少一次數(shù)據(jù)拷貝
需要2次拷貝,3次上下文切換

4、mmap與sendfile
mmap適合小數(shù)據(jù)量讀寫(xiě),sendfile適合大文件傳輸
mmap需要4次上下文切換,3次數(shù)據(jù)拷貝;sendfile需要3次上下文切換,最少2次數(shù)據(jù)拷貝
send可用利用DMA方式,減少CPU拷貝,mmap則不能(必須從內(nèi)核拷貝到Socket緩沖區(qū))
二、傳統(tǒng)IO傳輸文件代碼示例
1、服務(wù)端代碼
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) {
//文件讀取結(jié)束,退出循環(huán)
break;
}
total += read;
fileOutputStream.write(bytes, 0, read);
}
System.out.println("收到客戶端發(fā)送文件,總字節(jié)數(shù):" + 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("發(fā)送總字節(jié)數(shù):" + total + ", 總耗時(shí):" + (System.currentTimeMillis() - start));
dataOutputStream.close();
socket.close();
fileInputStream.close();
}
}3、控制臺(tái)出輸出
測(cè)試發(fā)送9M的壓縮文件,耗時(shí)在26ms左右
發(fā)送總字節(jié)數(shù):9428963, 總耗時(shí):26
三、NIO傳輸文件代碼示例
1、服務(wù)端代碼
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);
//創(chuàng)建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");
//循環(huán)讀取數(shù)據(jù),并存儲(chǔ)到硬盤(pán)
while (true) {
try {
int readCount = socketChannel.read(byteBuffer);
if (readCount == -1) {
//文件讀取結(jié)束
break;
}
total += readCount;
fileOutputStream.write(byteBuffer.array(),0,readCount);
//將buffer倒帶
byteBuffer.rewind();
} catch (IOException e) {
break;
}
}
System.out.println("收到客戶端發(fā)送文件,總字節(jié)數(shù):" + 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 {
//打開(kāi)一個(gè)SocketChannel并鏈接到服務(wù)器端
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 7000));
//打開(kāi)一個(gè)文件
FileChannel fileChannel = new FileInputStream("d:\\temp\\03.zip").getChannel();
//發(fā)送文件到服務(wù)器
long start = System.currentTimeMillis();
//在linux下,一次transferTo調(diào)用就可以完成傳輸
//在windows下,一次transferTo調(diào)用最多只能傳8M,大文件需要分段傳輸,需要注意傳輸位置
//transferTo底層使用零拷貝
long total = fileChannel.size();
long sended = 0;
while (sended < total) {
//從上一次傳輸位置繼續(xù)發(fā)送
long lenth = fileChannel.transferTo(sended, fileChannel.size(), socketChannel);
sended += lenth;
}
System.out.println("發(fā)送總字節(jié)數(shù):" + sended + ",總耗時(shí):" + (System.currentTimeMillis() - start));
//關(guān)閉channel
fileChannel.close();
socketChannel.close();
}
}3、控制臺(tái)出輸出
測(cè)試發(fā)送9M的壓縮文件,耗時(shí)在16ms左右
發(fā)送總字節(jié)數(shù):9428963,總耗時(shí):16
四、總結(jié)
使用零拷貝傳輸,性能明顯高于傳統(tǒng)IO傳輸
到此這篇關(guān)于Java圖文并茂詳解NIO與零拷貝的文章就介紹到這了,更多相關(guān)Java NIO與零拷貝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入理解Java設(shè)計(jì)模式之訪問(wèn)者模式
這篇文章主要介紹了JAVA設(shè)計(jì)模式之訪問(wèn)者模式的的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解2021-11-11
SpringCloud Feign遠(yuǎn)程調(diào)用實(shí)現(xiàn)詳解
Feign是Netflix公司開(kāi)發(fā)的一個(gè)聲明式的REST調(diào)用客戶端; Ribbon負(fù)載均衡、 Hystrⅸ服務(wù)熔斷是我們Spring Cloud中進(jìn)行微服務(wù)開(kāi)發(fā)非?;A(chǔ)的組件,在使用的過(guò)程中我們也發(fā)現(xiàn)它們一般都是同時(shí)出現(xiàn)的,而且配置也都非常相似2022-11-11
Hibernate Validation自定義注解校驗(yàn)的實(shí)現(xiàn)
這篇文章主要介紹了Hibernate Validation自定義注解校驗(yàn)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Java時(shí)間輪算法的實(shí)現(xiàn)代碼示例
本篇文章主要介紹了Java時(shí)間輪算法的實(shí)現(xiàn)代碼示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
idea中怎樣創(chuàng)建并運(yùn)行第一個(gè)java程序
這篇文章主要介紹了idea中怎樣創(chuàng)建并運(yùn)行第一個(gè)java程序問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
Springboot項(xiàng)目保存本地系統(tǒng)日志文件的實(shí)現(xiàn)方法
這篇文章主要介紹了Springboot項(xiàng)目保存本地系統(tǒng)日志文件的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
解決使用IDEA時(shí)跳轉(zhuǎn)到.class的問(wèn)題
這篇文章主要介紹了解決使用IDEA時(shí)跳轉(zhuǎn)到.class的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08

