淺析Java NIO 直接緩沖區和非直接緩沖區

定義

以上是書《深入理解java虛擬機》對直接內存的描述。直接緩沖區用的就是直接內存。

  1. java nio字節緩沖區要麼是直接的,要麼是非直接的。如果為直接字節緩沖區,則java虛擬機會盡最大努力直接在此緩沖區上執行本機的IO操作,也就是說,在每次調用基礎操作系統的一個本機IO操作前後,虛擬機都會盡量避免將內核緩沖區內容復制到用戶進程緩沖區中,或者反過來,盡量避免從用戶進程緩沖區復制到內核緩沖區中。
  2. 直接緩沖區可以通過調用該緩沖區類的allocateDirect(int capacity) 方法創建,此方法返回的緩沖區進行分配和取消分配所需的成本要高於非直接緩沖區。直接緩沖區的內容駐留在垃圾回收堆之外,因此他們對應用程序內存(JVM內存)需求不大。所以建議直接緩沖區要分配給那些大型,持久(就是緩沖區的數據會被重復利用)的緩沖區,一般情況下,最好僅在直接緩沖區能在程序性能帶來非常明顯的好處時才分配它們。
  3. 直接緩沖區還可以通過FileCHannel的map()方法將文件區域映射到內存中來創建,該方法返回MappedByteBuffer。java平臺的實現有助於通過JNI本地代碼創建直接字節緩沖區,如果以上這些緩沖區中某個緩沖區實例指向的是不可訪問的內存區域,則試圖方法該區域不會更改緩沖區的內容,並且會在訪問期間或者稍後的某個時間導致報出不確定性異常。
  4. 字節緩沖區是直接緩沖區還是非直接緩沖區可以通過調用其isDIrect()方法來判斷。

基於NIO的本地IO直接內存使用:

傳統IO對文件數據進行讀寫的流程:

流程說明(以上是應用程序完成一次文件拷貝的流程):

  1. 應用進程發起一個讀請求系統調用,然後進程切換到內核態。
  2. DMA把磁盤數據復制到內核緩沖區中。
  3. 內核把緩沖區數據復制到用戶緩沖區中。
  4. 進程切換到用戶態。
  5. 應用進程發起一個寫請求系統調用,然後進程切換到內核態。
  6. 內核把用戶緩沖區數據復制到內核緩沖區。
  7. DMA把內核緩沖區數據復制到磁盤上。
  8. 返回。

以上流程一共進行瞭四次上下文切換,四次數據拷貝。

使用mmap實現對傳統文件IO優化。

mmap:通過把內核緩沖區和用戶緩沖區映射在物理內存上映射為同一地址空間。這樣就不用對數據進行復制瞭。

這個是傳統的:

使用mmap後的IO大致流程:

數據拷貝次數從4次縮短到瞭兩次。

相關API demo以及比較:詳細api解釋可以查看Java NIO學習篇之通道FileChannel詳解

//使用直接緩沖區API進行一個700多M的文件進行拷貝
public static void testDirect(){
        try {
            long start = System.currentTimeMillis();
            FileChannel srcFileChannel = FileChannel.open(Paths.get("C:\\Users\\Yehaocong\\Desktop\\test\\95462017-1-64.flv"), StandardOpenOption.READ);
            FileChannel destFileChannel = FileChannel.open(Paths.get("C:\\Users\\Yehaocong\\Desktop\\test\\95462017-1-64-cp1.flv"),StandardOpenOption.CREATE,
                    StandardOpenOption.WRITE,StandardOpenOption.READ);
            MappedByteBuffer srcByteBuffer = srcFileChannel.map(FileChannel.MapMode.READ_ONLY,0,srcFileChannel.size());
            MappedByteBuffer descByteBuffer = destFileChannel.map(FileChannel.MapMode.READ_WRITE,0,srcFileChannel.size());
            descByteBuffer.put(srcByteBuffer);
            srcFileChannel.close();
            destFileChannel.close();
            System.out.println("直接緩沖區耗時:" + (System.currentTimeMillis()-start));
      } catch (IOException e) {
            e.printStackTrace();
        }
    }

public static void testSimpleIO(){
        try {
            long start = System.currentTimeMillis();
            FileChannel srcFileChannel = FileChannel.open(Paths.get("C:\\Users\\Yehaocong\\Desktop\\test\\95462017-1-64.flv"), StandardOpenOption.READ);
            FileChannel destFileChannel = FileChannel.open(Paths.get("C:\\Users\\Yehaocong\\Desktop\\test\\95462017-1-64-cp.flv"),StandardOpenOption.CREATE,
                    StandardOpenOption.WRITE,StandardOpenOption.READ);
            ByteBuffer byteBuffer = ByteBuffer.allocate((int) srcFileChannel.size());
            while (srcFileChannel.read(byteBuffer)!=-1){
                byteBuffer.flip();
                destFileChannel.write(byteBuffer);
                byteBuffer.clear();
            }


            srcFileChannel.close();
            destFileChannel.close();

            System.out.println("非緩沖區耗時:" + (System.currentTimeMillis()-start));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

執行結果:

 

到此這篇關於淺析Java NIO 直接緩沖區和非直接緩沖區的文章就介紹到這瞭,更多相關Java NIO 直接緩沖區和非直接緩沖區內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: