NIO提升系統性能

来源:http://www.cnblogs.com/lcngu/archive/2016/03/16/5281737.html
-Advertisement-
Play Games

在軟體系統中,I/O的速度要比記憶體的速度慢很多,因此I/O經常會稱為系統的瓶頸。所有,提高I/O速度,對於提升系統的整體性能有很大的作用。 在java標準的I/O中,是基於流的I/O的實現,即InputStream和OutPutStream,這種基於流的實現以位元組為基本單元,很容易實現各種過濾器。


  • 前言

  在軟體系統中,I/O的速度要比記憶體的速度慢很多,因此I/O經常會稱為系統的瓶頸。所有,提高I/O速度,對於提升系統的整體性能有很大的作用。

  在java標準的I/O中,是基於流的I/O的實現,即InputStream和OutPutStream,這種基於流的實現以位元組為基本單元,很容易實現各種過濾器。

  NIO和new I/O的簡稱,在java1.4納入JDK中,具有以下特征:

  1、為所有的原始類型提供(buffer)緩存支持;

  2、使用Charset作為字元集編碼解碼解決方案;

  3、增加了通道(Channel)對象,作為新的原始I/O抽象;

  4、支持鎖和記憶體訪問文件的文件訪問介面;

  5、提供了基於Selector的非同步網路I/O;

  NIO是基於塊(Block)的,它以塊為基本單位處理數據。在NIO中,最重要的兩個組件是buffer緩衝和channel通道。緩衝是一塊連續的記憶體區域,是NIO讀寫數據的中轉站。通道表示緩衝數據的源頭或目的地,它用於向緩衝讀取或寫入數據,是訪問緩衝的介面。通道和緩衝的關係如圖:

  

  • NIO中的Buffer類和Channel

  JDK為每一種java原生類型都提供了一種Buffer,除了ByteBuffer外,其他每一種Buffer都具有完全一樣的操作,除了操作類型不一樣以外。ByteBuffer可以用於絕大多數標準I/O操作的介面。

  在NIO中和Buffer配合使用的還有Channel。Channel是一個雙向通道,既可以讀也可以寫。有點類似Stream,但是Stream是單向的。應用程式不能直接對Channel進行讀寫操作,而必須通過Buffer來進行。

  下麵以一個文件複製為例,簡單介紹NIO的Buffer和Channel的用法,代碼如下:

 1 public class NioCopyFileTest {
 2     public static void main(String[] args) throws Exception {
 3         NioCopyFileTest.copy("test.txt", "test2.txt");
 4     }
 5     
 6     public static void copy(String resource,String destination) throws Exception{
 7         FileInputStream fis = new FileInputStream(resource); 
 8         FileOutputStream fos = new FileOutputStream(destination);
 9         
10         FileChannel inputFileChannel = fis.getChannel();//讀文件通道
11         FileChannel outputFileChannel = fos.getChannel();//寫文件通道
12         ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//讀寫數據緩衝
13         while(true){
14             byteBuffer.clear();
15             int length =  inputFileChannel.read(byteBuffer);//讀取數據
16             if(length == -1){
17                 break;//讀取完畢
18             }
19             byteBuffer.flip();
20             outputFileChannel.write(byteBuffer);//寫入數據
21         }
22         inputFileChannel.close();
23         outputFileChannel.close();
24     }
25 }

  代碼中註釋寫的很詳細了,輸入流和輸出流都對應一個Channel通道,將數據通過讀文件channel讀取到緩衝中,然後再通過寫文件channel寫入到緩衝中。這樣就完成了文件複製。註意:緩衝在文件傳輸中起到的作用十分大,可以緩解記憶體和硬碟之間的性能差異,提升系統性能。

  • Buffer的基本原理

  Buffer有三個重要的參數:位置(position)、容量(capactiy)和上限(limit)。這三個參數的含義如下圖:

  下麵例子很好的解釋了Buffer的工作原理:

 1      ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩衝區大小為15
 2         System.out.println("position:"+buffer.position()+"limit:"+buffer.limit()+"capacity"+buffer.capacity());
 3         for (int i = 0; i < 10; i++) {
 4             buffer.put((byte) i);
 5         }
 6         System.out.println("position:"+buffer.position()+"limit:"+buffer.limit()+"capacity"+buffer.capacity());
 7         buffer.flip();//重置position
 8         for (int i = 0; i < 5; i++) {
 9             System.out.println(buffer.get());
10         }
11         System.out.println("position:"+buffer.position()+"limit:"+buffer.limit()+"capacity"+buffer.capacity());
12         buffer.flip();
13         System.out.println("position:"+buffer.position()+"limit:"+buffer.limit()+"capacity"+buffer.capacity());

  以上代碼,先分配了15個位元組大小的緩衝區。在初始階段,position為0,capacity為15,limit為15。註意,position是從0開始的,所以索引為15的位置實際上是不存在的。

  接著往緩衝區放入10個元素,position始終指向下一個即將放入的位置,所有position為10,capacity和limit依然為15。

  進行flip()操作,會重置position的位置,並且將limit設置到當前position的位置,這時Buffer從寫模式進入讀模式,這樣就可以防止讀操作讀取到沒有進行操作的位置。所有此時,position為0,limit為10,capacity為15。

  接著進行五次讀操作,讀操作會設置position的位置,所以,position為5,limit為10,capacity為15。

  在進行一次flip()操作,此時可想而知position為0,limit為5,capacity為15。

  • Buffer的相關操作

  Buffer是NIO中最核心的對象,它的一系列的操作和使用也需要重點掌握,這裡簡單概括一下,也可以參考相關API查看。

  1、Buffer的創建:

  buffer的常見有兩種方式,使用靜態方法allocate()從堆中分配緩衝區,或者從一個既有數組中創建緩衝區。

1 ByteBuffer buffer = ByteBuffer.allocate(1024);//從堆中分配
2 byte[] arrays = new byte[1024];//從既有數組中創建
3 ByteBuffer buffer2 = ByteBuffer.wrap(arrays);

  2、重置或清空緩衝區:

  buffer還提供了一些用於重置和清空緩衝區的方法:rewind(),clear(),flip()。它們的作用如下:

  3、讀寫緩衝區:

  對Buffer對象進行讀寫操作是Buffer最重要的操作,buffer提供了許多讀寫操作的緩衝區。具體參考API。

  4、標誌緩衝區

  標誌(mark)緩衝區是一個在數據處理時很有用的功能,它就像書簽一樣,可以在數據處理中隨時記錄當前位置,然後再任意時刻回到這個位置,從而簡化或加快數據處理的流程。相關函數為:mark()和reset()。mark()用於記錄當前位置,reset()用於恢復到mark標記的位置。

  代碼如下:

 1 ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩衝區大小為15
 2         for (int i = 0; i < 10; i++) {
 3             buffer.put((byte) i);
 4         }
 5         buffer.flip();//重置position
 6         for (int i = 0; i < buffer.limit(); i++) {
 7             System.out.print(buffer.get());
 8             if(i==4){
 9                 buffer.mark();
10                 System.out.print("mark at"+i);
11             }
12         }
13         System.out.println();
14         buffer.reset();
15         while(buffer.hasRemaining()){
16             System.out.print(buffer.get());
17 }

  輸出結果:

1 01234mark at456789
2 56789

  5、複製緩衝區

  複製緩衝區是以原緩衝區為基礎,生成一個完全一樣的緩衝區。方法為:duplicate()。這個函數對於處理複雜的Buffer數據很有好處。因為新生成的緩衝區和元緩衝區共用相同的記憶體數據。並且,任意一方的改動都是互相可見的,但是兩者又各自維護者自己的position、limit和capacity。這大大增加了程式的靈活性,為多方同時處理數據提供了可能。

  代碼如下:

 1         ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩衝區大小為15
 2         for (int i = 0; i < 10; i++) {
 3             buffer.put((byte) i);
 4         }
 5         ByteBuffer buffer2 = buffer.duplicate();//複製當前緩衝區
 6         System.out.println("after buffer duplicate");
 7         System.out.println(buffer);
 8         System.out.println(buffer2);
 9         buffer2.flip();
10         System.out.println("after buffer2 flip");
11         System.out.println(buffer);
12         System.out.println(buffer2);
13         buffer2.put((byte)100);
14         System.out.println("after buffer2 put");
15         System.out.println(buffer.get(0));
16         System.out.println(buffer2.get(0));             

  輸出結果如下:

1 after buffer duplicate
2 java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
3 java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
4 after buffer2 flip
5 java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
6 java.nio.HeapByteBuffer[pos=0 lim=10 cap=15]
7 after buffer2 put
8 100
9 100

  6、緩衝區分片

  緩衝區分片使用slice()方法,它將現有的緩衝區創建新的子緩衝區,子緩衝區和父緩衝區共用數據,子緩衝區具有完整的緩衝區模型結構。當處理一個buffer的一個片段時,可以使用一個slice()方法取得一個子緩衝區,然後就像處理普通緩衝區一樣處理這個子緩衝區,而無需考慮邊界問題,這樣有助於系統模塊化。 

 1     ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩衝區大小為15
 2         for (int i = 0; i < 10; i++) {
 3             buffer.put((byte) i);
 4         }
 5         buffer.position(2);
 6         buffer.limit(6);
 7         ByteBuffer subBuffer = buffer.slice();//複製緩衝區
 8         for (int i = 0; i < subBuffer.limit(); i++) {
 9             byte b = subBuffer.get(i);
10             b=(byte) (b*10);
11             subBuffer.put(i, b);
12         }
13         buffer.limit(buffer.capacity());
14         buffer.position(0);
15         for (int i = 0; i < buffer.limit(); i++) {
16             System.out.print(buffer.get(i)+" ");
17         }

  輸出結果: 

1 0 1 20 30 40 50 6 7 8 9 0 0 0 0 0 

  7、只讀緩衝區

  可以使用緩衝區對象的asReadOnlyBuffer()方法得到一個與當前緩衝區一致的,並且共用記憶體數據的只讀緩衝區,只讀緩衝區對於數據安全非常有用。使用只讀緩衝區可以保證數據不被修改,同時,只讀緩衝區和原始緩衝區是共用記憶體塊的,因此,對於原始緩衝區的修改,只讀緩衝區也是可見的。

  代碼如下:

 1      ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩衝區大小為15
 2         for (int i = 0; i < 10; i++) {
 3             buffer.put((byte) i);
 4         }
 5         ByteBuffer readBuffer = buffer.asReadOnlyBuffer();
 6         for (int i = 0; i < readBuffer.limit(); i++) {
 7             System.out.print(readBuffer.get(i)+" ");
 8         }
 9         System.out.println();
10         buffer.put(2, (byte)20);
11         for (int i = 0; i < readBuffer.limit(); i++) {
12             System.out.print(readBuffer.get(i)+" ");
13         }

  結果:

1 0 1 2 3 4 5 6 7 8 9 0 0 0 0 0 
2 0 1 20 3 4 5 6 7 8 9 0 0 0 0 0 

  由此可見,只讀緩衝區並不是原始緩衝區在某一時刻的快照,而是和原始緩衝區共用記憶體數據的。當修改只讀緩衝區時,會報ReadOnlyBufferException異常。

  8、文件映射到記憶體:

  NIO提供了一種將文件映射到記憶體的方法進行I/O操作,它可以比常規的基於流的I/O快很多。這個操作主要是由FileChannel.map()方法實現的。

  使用文件映射的方式,將文本文件通過FileChannel映射到記憶體中。然後在記憶體中讀取文件內容。還可以修改Buffer,將實際數據寫到對應的硬碟中。

1      RandomAccessFile raf = new RandomAccessFile("D:\\test.txt", "rw");
2         FileChannel fc = raf.getChannel();
3         MappedByteBuffer mbf = fc.map(MapMode.READ_WRITE, 0, raf.length());//將文件映射到記憶體
4         while(mbf.hasRemaining()){
5             System.out.println(mbf.get());
6         }
7         mbf.put(0,(byte)98);//修改文件
8         raf.close();

  9、處理結構化數據

  NIO還提供了處理結構化數據的方法,稱為散射和聚集。散射是將一組數據讀入到一組buffer中,聚集是將數據寫入到一組buffer中。聚集和散射的基本使用方法和對單個buffer操作的使用方法類似。這一組緩衝區類似於一個大的緩衝區。

  散射/聚集IO對處理結構化數據非常有用。例如,對於一個具有固定格式的文件的讀寫,在已知文件具體結構的情況下,可以構造若幹個符合文件結構的buffer,使得各個buffer的大小恰好符合文件各段結構的大小。

  例如,將"姓名:張三,年齡:18",通過聚集寫創建該文件,然後再通過散射都來解析。

 1 ByteBuffer nameBuffer = ByteBuffer.wrap("姓名:張三,".getBytes("utf-8"));
 2         ByteBuffer ageBuffer = ByteBuffer.wrap("年齡:18".getBytes("utf-8"));
 3         int nameLength = nameBuffer.limit();
 4         int ageLength = ageBuffer.limit();
 5         ByteBuffer[] bufs = new ByteBuffer[]{nameBuffer,ageBuffer};
 6         File file = new File("D:\\name.txt");
 7         if(!file.exists()){
 8             file.createNewFile();
 9         }
10         FileOutputStream fos = new FileOutputStream(file);
11         FileChannel channel = fos.getChannel();
12         channel.write(bufs);
13         channel.close();
14         
15         ByteBuffer nameBuffer2 = ByteBuffer.allocate(nameLength);
16         ByteBuffer ageBuffer2 = ByteBuffer.allocate(ageLength);
17         ByteBuffer[] bufs2 = new ByteBuffer[]{nameBuffer2,ageBuffer2};
18         FileInputStream fis = new FileInputStream("D:\\name.txt");
19         FileChannel channel2 = fis.getChannel();
20         channel2.read(bufs2);
21         String name = new String(bufs2[0].array(),"utf-8");
22         String age = new String(bufs2[1].array(),"utf-8");
23         
24         System.out.println(name+age);

  通過和通道的配合使用,可以簡化Buffer對於結構化數據處理的難度。

  註意,ByteBuffer是將文件一次性讀入記憶體再做處理,而Stream方式則是邊讀取文件邊處理數據,這也是兩者性能差異的主要原因。

  • 直接記憶體訪問

  NIO的Buffer還提供了一個可以直接訪問系統物理記憶體的類--DirectBuffer。普通的ByteBuffer依然在JVM堆上分配空間,其最大記憶體,受最大堆的限制。而DirecBuffer直接分配在物理記憶體中,並不占用堆空間。創建DirectBuffer的方法是:ByteBuffer.allocateDirect(capacity)。

  在對普通的ByteBuffer的訪問,系統總會使用一個"內核緩衝區"進行間接操作。而ByteBuffer所處的位置,就相當於這個"內核緩衝區"。因此,DirecBuffer是一種更加接近底層的操作。

  DirectBuffer的訪問速度遠高於ByteBuffer,但是其創建和銷毀所消耗的時間卻遠大於ByteBuffer。在需要頻繁創建和銷毀Buffer的場合,顯然不適合DirectBuffer的使用,但是如果能將DirectBuffer進行復用,那麼在讀寫頻繁的場合下,它完全可以大幅度改善系統性能。

 


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

-Advertisement-
Play Games
更多相關文章
  • 演算法:用原來字母後面的第四個字母替代原來的字母。明文:China 密文:Glmre
  • Implement atoi to convert a string to an integer. Hint: Carefully consider all possible input cases. If you want a challenge, please do not see below
  • 上一篇從整體上介紹了 介面,從上一篇我們知道了 框架的最頂層實現是 類, 工廠類中提供的 、`newFixedThreadPool newCachedThreadPool ThreadPoolExecutor ThreadPoolExecutor`線程池的運行過程。 1.線程池狀態 既然要講運行過程
  • 本篇分為兩部分: 與別的語言不同,Swift 支持運算符的重載,運算符指的是“+,-,%,*”等操作運算符,從而使一些操作更加簡便。 在開發中,我們對於一些基本運算通常是這樣寫的: v3 的輸出結果為:3,7 但是,當這種二位數組過多時,如果我們依然用這種方式,我們的代碼將變得異常臃腫,程式員也會變
  • 首先要將#include <atlimage.h>加進來,開始時我加到stdafx.h中,但一直提示windows.h被重覆引入的問題,後將其加在別的頭文件中,就可以了.. --! 一、圖片的載入 如果需要在界面上顯示的是已經存在的圖片,那麼需要將待顯示的圖片載入至CImage對象之中,CImage
  • PHP的預設機制:每一次php請求,會有1/100的概率(預設值)觸發“session回收”。如果“session回收”發生,那就會檢查/tmp/sess_*的文件,如果最後的修改時間到現在超過了1440秒(gc_maxlifetime的值),就將其刪除,意味著這些session過期失效 文件一般為
  • java oop 1.面向過程的結構化程式設計弊端:方法和數據結構都是毫無規律的定義在程式中任何位置 方法定義和方法要處理的數據結構也都是分開定義2.對象:每new一次,就創建1個新對象,和原來的對象之間沒有影響3.需求中的名詞:對象 動詞:方法4.方法簽名:包含方法名和參數類型類表“順序”, 重載
  • 字元串表示的「true」轉換為布爾型的「true」
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...