NIO基礎之Buffer

来源:https://www.cnblogs.com/jalja/archive/2019/05/13/10854511.html
-Advertisement-
Play Games

直接在堆外分配一個記憶體(即,native memory)來存儲數據,程式通過JNI直接將數據讀/寫到堆外記憶體中。因為數據直接寫入到了堆外記憶體中,所以這種方式就不會再在JVM管控的堆內再分配記憶體來存儲數據了,也就不存在堆內記憶體和堆外記憶體數據拷貝的操作了。這樣在進行I/O操作時,只需要將這個堆外記憶體地址... ...


java.io 核心概念是流,即面向流的編程,在java中一個流只能是輸入流或者輸出流,不能同時具有兩個概念。

java.nio核心是 selector、Channel、Buffer ,是面向緩衝區(buffer)或者面向塊block。

一、Buffer 

Buffer本身是一個記憶體塊,底層是數組,數據的讀寫都是通過Buffer類實現的。即同一個Buffer即可以寫數據也可以讀數據,通過intBuffer.flip()方法進行Buffer位置狀態的翻轉。JAVA中的8中基本類型都有各自對應的Buffer。

緩衝區buffer主要是和通道數據交互,即從通道中讀入數據到緩衝區,和從緩衝區中把數據寫入到通道中,通過這樣完成對數據的傳輸。 它通過幾個變數來保存這個數據的當前位置狀態。

Buffer中的四個核心變數

  • 容量(Capacity):緩衝區能夠容納的數據元素的最大數量。這一個容量在緩衝區創建時被設定,並且永遠不能改變。
  • 界限(Limit):指定還有多少數據需要取出(在從緩衝區寫入通道時),或者還有多少空間可以放入數據(在從通道讀入緩衝區時)。
  • 位置(Position):指定了下一個將要被寫入或者讀取的元素索引,它的值由get()/put()方法自動更新,在新創建一個Buffer對象時,position被初始化為0。
  • 標記(Mark):下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新

get()方法從緩衝區中讀取數據寫入到輸出通道,這會導致position的增加而limit保持不變,但position不會超過limit的值。

flip()方法 把limit設置為當前的position值 並且把position設置為 0

clear()方法將Buffer恢復到初始化狀態

public class BufferTest {
    public static void main(String[] args) throws IOException {
        ByteBufferTest();
    }
    private static void  ByteBufferTest(){
        //分配新的byte緩衝區,參數為緩衝區容量
        //新緩衝區的當前位置將為零,其界限(限制位置)將為其容量,它將具有一個底層實現數組,其數組偏移量將為零。
        ByteBuffer byteBuffer=ByteBuffer.allocate(10);
        output("初始化緩衝區:",byteBuffer);
        for(int i=0;i<byteBuffer.capacity()-1;i++){
            byteBuffer.put(Byte.parseByte(new SecureRandom().nextInt(20)+""));
        }
        output("寫入緩衝區9個byte:",byteBuffer);
        byteBuffer.flip();
        output("使用flip重置元素位置:",byteBuffer);
        while (byteBuffer.hasRemaining()){
            System.out.print(byteBuffer.get()+"|");
        }
        System.out.print("\n");
        output("使用get讀取元素:",byteBuffer);
        byteBuffer.clear();
        output("恢復初始化態clear:",byteBuffer);

    }
    private static void output(String step, Buffer buffer) {
        System.out.println(step + " : ");
        System.out.print("capacity: " + buffer.capacity() + ", ");
        System.out.print("position: " + buffer.position() + ", ");
        System.out.println("limit: " + buffer.limit());
        System.out.println("mark: " + buffer.mark());
        System.out.println();
    }
}
初始化緩衝區: : 
capacity: 10, position: 0, limit: 10
mark: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]

寫入緩衝區9個byte: : 
capacity: 10, position: 9, limit: 10
mark: java.nio.HeapByteBuffer[pos=9 lim=10 cap=10]

使用flip重置元素位置: : 
capacity: 10, position: 0, limit: 9
mark: java.nio.HeapByteBuffer[pos=0 lim=9 cap=10]

讀取元素:1|讀取元素:16|讀取元素:12|讀取元素:0|讀取元素:17|讀取元素:5|讀取元素:4|讀取元素:13|讀取元素:18|
使用get讀取元素後: : 
capacity: 10, position: 9, limit: 9
mark: java.nio.HeapByteBuffer[pos=9 lim=9 cap=10]

恢復初始化態clear: : 
capacity: 10, position: 0, limit: 10
mark: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]

 ByteBuffer.wrap( array ):將一個現有的數組,包裝為緩衝區對象

 buffer.slice():創建子緩衝區,子緩衝區與原緩衝區是數據共用的

buffer.position( 3 );
buffer.limit( 7 );
ByteBuffer slice = buffer.slice();

 只讀緩衝區:ByteBuffer readonly = buffer.asReadOnlyBuffer();

     只讀緩衝區非常簡單,可以讀取它們,但是不能向它們寫入數據。可以通過調用緩衝區的asReadOnlyBuffer()方法,將任何常規緩衝區轉 換為只讀緩衝區,這個方法返回一個與原緩衝區完全相同的緩衝區,並與原緩衝區共用數據,只不過它是只讀的。如果原緩衝區的內容發生了變化,只讀緩衝區的內容也隨之發生變化。

    如果嘗試修改只讀緩衝區的內容,則會報ReadOnlyBufferException異常。只讀緩衝區對於保護數據很有用。創建一個只讀的緩衝區可以保證該緩衝區不會被修改。只可以把常規緩衝區轉換為只讀緩衝區,而不能將只讀的緩衝區轉換為可寫的緩衝區。

 

二、直接緩衝區 DirectByteBuffer

   直接在堆外分配一個記憶體(即,native memory)來存儲數據,程式通過JNI直接將數據讀/寫到堆外記憶體中。因為數據直接寫入到了堆外記憶體中,所以這種方式就不會再在JVM管控的堆內再分配記憶體來存儲數據了,也就不存在堆內記憶體和堆外記憶體數據拷貝的操作了。這樣在進行I/O操作時,只需要將這個堆外記憶體地址傳給JNI的I/O的函數就好了。底層的數據其實是維護在操作系統的記憶體中,而不是jvm里,DirectByteBuffer里維護了一個引用address指向了數據,從而操作數據。實現zero copy(零拷貝)。

       間接記憶體HeapByteBuffer:對於HeapByteBuffer,數據的分配存儲都在jvm堆上,當需要和io設備打交道的時候,會將jvm堆上所維護的byte[]拷貝至堆外記憶體,然後堆外記憶體直接和io設備交互。外設之所以要把jvm堆里的數據copy出來再操作,不是因為操作系統不能直接操作jvm記憶體,而是因為jvm在進行gc(垃圾回收)時,會對數據進行移動,一旦出現這種問題,外設就會出現數據錯亂的情況。

直接緩衝區的創建:ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );

DirectByteBuffer的初始化:

 DirectByteBuffer(int cap) {                   // package-private
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        // 保留總分配記憶體(按頁分配)的大小和實際記憶體的大小
        Bits.reserveMemory(size, cap);
        long base = 0;
        try {
             // 通過unsafe.allocateMemory分配堆外記憶體,並返回堆外記憶體的基地址
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
         // 構建Cleaner對象用於跟蹤DirectByteBuffer對象的垃圾回收,以實現當DirectByteBuffer被垃圾回收時,堆外記憶體也會被釋放
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }

 // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    //address就是堆外記憶體創建好後返回給JVM的地址,JVM記憶體需要維護的只是DirectByteBuffer對象,而具體數據的管理是由操作系統來管理的
    long address;

什麼情況下使用堆外記憶體

  • 堆外記憶體適用於生命周期中等或較長的對象。( 如果是生命周期較短的對象,在YGC的時候就被回收了,就不存在大記憶體且生命周期較長的對象在FGC對應用造成的性能影響 )。
  • 直接的文件拷貝操作,或者I/O操作。直接使用堆外記憶體就能少去記憶體從用戶記憶體拷貝到系統記憶體的操作,因為I/O操作是系統內核記憶體和設備間的通信,而不是通過程式直接和外設通信的。
  • 同時,還可以使用 池+堆外記憶體 的組合方式,來對生命周期較短,但涉及到I/O操作的對象進行堆外記憶體的再使用。( Netty中就使用了該方式 )

兩種方式的效率比較:

private static void directByteBufferTest()throws IOException{
        long start=System.currentTimeMillis();
        FileInputStream is=new FileInputStream("F:\\logs\\1g.rar");
        FileOutputStream fos=new FileOutputStream("F:\\logs\\2g.rar");
        FileChannel fcIs,fcOut;
        fcIs=is.getChannel();
        fcOut=fos.getChannel();
        ByteBuffer directByteBuffer= ByteBuffer.allocateDirect(2048);
        while (fcIs.read(directByteBuffer)!=-1){
            directByteBuffer.flip();
            fcOut.write(directByteBuffer);
            directByteBuffer.clear();
        }
        is.close();
        fos.close();
        long end=System.currentTimeMillis();
        System.out.println("DirectByteBuffer需要時間:"+(end-start));
    }
    private static void heapByteBufferTest()throws IOException{
        long start=System.currentTimeMillis();
        FileInputStream is=new FileInputStream("F:\\logs\\1g.rar");
        FileOutputStream fos=new FileOutputStream("F:\\logs\\3g.rar");
        FileChannel fcIs,fcOut;
        fcIs=is.getChannel();
        fcOut=fos.getChannel();
        ByteBuffer directByteBuffer= ByteBuffer.allocate(2048);
        while (fcIs.read(directByteBuffer)!=-1){
            directByteBuffer.flip();
            fcOut.write(directByteBuffer);
            directByteBuffer.clear();
        }
        is.close();
        fos.close();
        long end=System.currentTimeMillis();
        System.out.println("HeapByteBuffer需要時間:"+(end-start));
    }

17行輸出:DirectByteBuffer需要時間:30456

35行輸出:HeapByteBuffer需要時間:45285

 三、記憶體映射文件I/O   MappedByteBuffer

 

 記憶體映射文件I/O是一種讀和寫文件數據的方法,它可以比常規的基於流或者基於通道的I/O快的多。記憶體映射文件I/O是通過使文件中的數據出現為 記憶體數組的內容來完成的,這其初聽起來似乎不過就是將整個文件讀到記憶體中,但是事實上並不是這樣。一般來說,只有文件中實際讀取或者寫入的部分才會映射到記憶體中。

FileChannel提供了map方法來把文件影射為記憶體映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件的從position開始的size大小的區域映射為記憶體映像文件,映射記憶體緩衝區是個直接緩衝區,繼承自ByteBuffer,但相對於ByteBuffer,它有更多的優點 讀取快 寫入快  隨時隨地寫入;

mode指出了 可訪問該記憶體映像文件的方式:            
    1、READ_ONLY,(只讀): 試圖修改得到的緩衝區將導致拋出 ReadOnlyBufferException.(MapMode.READ_ONLY)

    2、READ_WRITE(讀/寫): 對得到的緩衝區的更改最終將傳播到文件;該更改對映射到同一文件的其他程式不一定是可見的。 (MapMode.READ_WRITE)
    3、PRIVATE(專用): 對得到的緩衝區的更改不會傳播到文件,並且該更改對映射到同一文件的其他程式也不是可見的;相反,會創建緩衝區已修改部分的專用副本。 (MapMode.PRIVATE)

MappedByteBuffer 中的三個方法:

    a. fore();緩衝區是READ_WRITE模式下,此方法對緩衝區內容的修改強行寫入文件
    b. load()將緩衝區的內容載入記憶體,並返回該緩衝區的引用
    c. isLoaded()如果緩衝區的內容在物理記憶體中,則返回真,否則返回假

使用MappedByteBuffer 將數據寫入文件:

private static void mappedOutFile()throws IOException{
        String str="I Love MappedByteBuffer";
        RandomAccessFile raf = new RandomAccessFile( filePath, "rw" );
        FileChannel fc = raf.getChannel();
        byte [] msg=str.getBytes("UTF-8");
        MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, 0, msg.length);
        mbb.put(msg);
        fc.write(mbb);
        raf.close();
    }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用構造函數的主要問題,就是每個方法都要在實例上重新創建一遍。 探討構造函數內部的方法(或函數)的問題,首先看下兩個實例化後的屬性或方法是否相等。 方法的值相等,因為傳參一致可以把構造函數里的方法(或函數)用 new Function()方法來代替,得到一樣的效果,更加證明,他們最終判斷的是引用地址 ...
  • 表單:用來收集用戶的信息; 1、表單框: <form name=名" method="post/get" action=""></form> 2、文本框: <input type ="text" value="預設值"> 3、密碼框: <input type ="password"/> <input ...
  • 1、雙倍浮動BUG: 描述:塊狀元素設置了float屬性後,又設置了橫向的margin值,在IE6下顯示的margin值要比設置的值大; 解決方案:給float的元素添加 display:inline;將其轉換為內聯元素; 2、表單元素行高不一致: 解決方案: ①、給表單元素添加vertical-a ...
  • 將10086註冊到10087上: 再在10086服務的基礎上複製一個Eureka的服務,埠為10087,將其註冊到10086上: application-name的名稱保持一致,只是一個服務的兩個實例。 兩個都啟動: 10087: 10086: 如果有超過3台以上的集群,url的地址就是如下這種寫 ...
  • 什麼是Zookeeper臨時順序節點? 例如 : / 動物 植物 貓 倉鼠 荷花 松樹 Zookeeper的數據存儲結構就像一棵樹,這棵樹由節點組成,這種節點叫做Zonde.# Znode分為四種類型 : 1.持久節點(PERSISTENT) 預設的節點類型.創建節點的客戶端與zookeeper斷開 ...
  • Spring Cloud常用組件: 架構圖: 版本對應關係: ...
  • 環境: 前端 vue ip地址:192.168.1.205 後端 springboot2.0 ip地址:192.168.1.217 主要開發後端。 問題: 首先登陸成功時將用戶存在session中,後續請求在將用戶從session中取出檢查。後續請求取出的用戶都為null。 解決過程: 首先發現se ...
  • 有-W選項。 python -W ignore foo.py 有-W選項。 python -W ignore foo.py 有-W選項。 python -W ignore foo.py 所屬網站分類: python基礎 > 綜合&其它 作者:jiem 鏈接:http://www.pythonheid ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...