4 java NIO 零拷貝深入剖析用戶空間與內核空間切換方式(圖解)

業務場景:將磁盤上的文件讀取到內存裏面發送給用戶 通過網絡 (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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章