1、前言java
在筆者打算學習Netty框架時,發現頗有必要先學習NIO,所以便有了本博文,首先介紹的是NIO中的緩衝。數組
2、緩衝安全
2.1 層次結構圖多線程
除了布爾類型外,其餘基本類型都有相對應的緩衝區類,其繼承關係層次圖以下。框架
其中,Buffer是全部類的父類,Buffer中也規定了全部緩衝區的共同行爲。函數
2.2 緩衝區基礎post
緩衝區是包在一個對象內的基本數據元素數組,其有四個重要屬性學習
容量( Capacity):緩衝區可以容納的數據元素的最大數量,容量在緩衝區建立時被設定,而且永遠不能被改變。
spa
上界(Limit):緩衝區的第一個不能被讀或寫的元素。或者說,緩衝區中現存元素的計數。
操作系統
位置(Position):下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新。
標記(Mark):一個備忘位置。調用 mark( )來設定 mark = postion。調用 reset( )設定 position = mark。標記在設定前是未定義的(undefined)。
四個屬性之間的關係以下
0 <= mark <= position <= limit <= capacity
以下圖展現了一個容量爲10的ByteBuffer邏輯視圖。
其中,mark未被設定,position初始爲0,capacity爲10,limit爲10,第一個元素存放至position爲0的位置,capacity不變,其餘三個屬性會變化。position在調用 put()時,會自動指出了下一個數據元素應該被插入的位置,或者當 get()被調用時指出下一個元素應從何處取出。
當put完數據後須要讀取時,須要調用flip函數,其將limit設置爲position,而後將position設置爲0, 以後開始讀取。
當對緩衝區調用兩次flip函數時,緩衝區的大小變爲0。由於第二次的limit也設置爲0,position爲0,所以緩衝區大小爲0。
緩衝區並非多線程安全的。若是想以多線程同時存取特定的緩衝區,則須要在存取緩衝區以前進行同步。
下面是一個簡單的緩衝區讀寫的示例
import java.nio.CharBuffer; /** * Created by LEESF on 2017/4/15. */ public class BufferDemo { public static void main(String[] args) { CharBuffer buffer = CharBuffer.allocate(5); buffer.put('H'); buffer.put('E'); buffer.put('L'); buffer.put('L'); buffer.put('O'); buffer.flip(); while (buffer.hasRemaining()) { System.out.print(buffer.get()); } } }
運行結果以下
HELLO
當對緩衝區進行比較時,斷定兩個緩衝區相同充要條件以下
· 兩個對象類型相同。包含不一樣數據類型的 buffer 永遠不會相等,並且 buffer毫不會等於非 buffer 對象。
· 兩個對象都剩餘一樣數量的元素。Buffer 的容量不須要相同,並且緩衝區中剩餘數據的索引也沒必要相同。但每一個緩衝區中剩餘元素的數目(從位置到上界)必須相同。
· 在每一個緩衝區中應被 get()函數返回的剩餘數據元素序列必須一致。
若是不知足以上任意條件, 兩個緩衝區的比較就會返回 false。
當兩個緩衝區不同長,進行比較時,若是一個緩衝區在不相等元素髮現前已經被耗盡,較短的緩衝區被認爲是小於較長的緩衝區。
當緩衝區與數組進行交互時,若是緩衝區中的數據不夠徹底填滿數組,就會獲得一個異常。這意味着若是想將一個小型緩衝區傳入一個大型數組,就必須明確地指定緩衝區中剩餘的數據長度。若是緩衝區有足夠的空間接受數組中的數據( buffer.remaining()>myArray.length),數據將會被複制到從當前位置開始的緩衝區,而且緩衝區位置會被提早所增長數據元素的數量。若是緩衝區中沒有足夠的空間,那麼不會有數據被傳遞,同時拋出BufferOverflowException 異常。
2.3 建立緩衝區
新的緩衝區由分配(allocate)或包裝(wrap)操做建立的。分配操做建立一個緩衝區對象並分配一個私有的空間來儲存指定容量大小的數據。包裝操做建立一個緩衝區對象可是不分配任何空間來儲存數據元素,使用所提供的數組做爲存儲空間來儲存緩衝區中的數據。
使用分配方式建立緩衝區以下 ,如建立容量大小爲100的CharBuffer。
CharBuffer charBuffer = CharBuffer.allocate (100);
使用包裝方式建立緩衝區以下
char [] myArray = new char [100]; CharBuffer charbuffer = CharBuffer.wrap (myArray);
上述代碼構造了一個新的緩衝區對象,但數據元素會存在於數組中。這意味着經過調用put()函數形成的對緩衝區的改動會直接影響這個數組,並且對這個數組的任何改動也會對這個緩衝區對象可見。
帶有 offset 和 length 做爲參數的 wrap()函數版本則會構造一個按照指定的 offset 和 length 參數值初始化位置和上界的緩衝區。
CharBuffer charbuffer = CharBuffer.wrap (myArray, 12, 42);
上述代碼建立了一個 position 值爲 12, limit 值爲 54(12 + 42),容量爲 myArray.length 的緩衝區。
2.4 複製緩衝區
當一個管理其餘緩衝器所包含的數據元素的緩衝器被建立時,這個緩衝器被稱爲視圖緩衝器,而視圖緩衝器老是經過調用已存在的存儲器實例中的函數來建立。
如Duplicate()函數建立了一個與原始緩衝區類似的新的緩衝區,兩個緩衝區共享數據元素,擁有一樣的容量,但每一個緩衝區擁有各自的位置,上界和標記屬性。對一個緩衝區內的數據元素所作的改變會反映在另一個緩衝區上。這一副本緩衝區具備與原始緩衝區一樣的數據視圖。若是原始的緩衝區爲只讀,或者爲直接緩衝區,新的緩衝區將繼承這些屬性。即複製一個緩衝區會建立一個新的 Buffer 對象,但並不複製數據,原始緩衝區和副本都會操做一樣的數據元素。
以下面代碼片斷
CharBuffer buffer = CharBuffer.allocate (8); buffer.position (3).limit (6).mark( ).position (5); CharBuffer dupeBuffer = buffer.duplicate( ); buffer.clear( );
會建立一個以下圖所示的緩衝視圖
能夠看到,複製的緩衝繼承了四個屬性值,操做的底層都是同一份數據,每一個視圖對數據的操做都會反映到另一個視圖上,以下述代碼可驗證。
import java.nio.CharBuffer; /** * Created by LEESF on 2017/4/13. */ public class AllocateDemo { public static void main(String[] args) { CharBuffer buffer = CharBuffer.allocate (8); buffer.put('L'); buffer.put('E'); buffer.put('E'); buffer.put('S'); buffer.put('F'); buffer.position (3).limit (6).mark( ).position (5); CharBuffer dupeBuffer = buffer.duplicate( ); buffer.clear( ); dupeBuffer.flip(); System.out.println(dupeBuffer.position()); System.out.println(dupeBuffer.limit()); System.out.println(dupeBuffer.get()); buffer.put('Y'); buffer.put('D'); buffer.flip(); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.get()); System.out.println(dupeBuffer.get()); } }
運行結果以下
0 5 L 0 2 Y D
能夠看到buffer視圖對數據的寫入影響了dupeBuffer的數據獲取。
2.5 字節緩衝區
字節是操做系統及其 I/O 設備使用的基本數據類型。當在 JVM 和操做系統間傳遞數據時,也是使用字段進行傳遞。
每一個基本數據類型都是以連續字節序列的形式存儲在內存中,如32 位的 int 值0x037fb4c7(十進制的 58,700,999)可能會以下圖所示的被存儲內存字節中(內存地址從左往右增長)。
多字節數值被存儲在內存中的方式通常被稱爲 endian-ness(字節順序),若是數字數值的最高字節——big end(大端),位於低位地址,那麼系統就是大端字節順序,若是最低字節最早保存在內存中,那麼小端字節順序,以下圖所示。
默認字節順序老是 ByteBuffer.BIG_ENDIAN,不管系統的固有字節順序是什麼。Java 的默認字節順序是大端字節順序。
爲了解決非直接緩衝區(如經過wrap()函數所建立的被包裝的緩衝區)的效率問題,引入了直接緩衝區,直接緩衝區使用的內存是經過調用本地操做系統方面的代碼分配的,繞過了標準 JVM 堆棧,所以效率更高。ByteBuffer中存在isDirect方法判斷緩衝區是不是直接緩衝區。
字節緩衝區能夠轉化爲其餘不一樣類型的緩衝區,如CharBuffer、ShortBuffer等,轉化後的緩衝區都只是原來緩衝區的視圖,即有獨立的四個屬性值,可是共享數據元素。
字節緩衝區能夠直接存放或者取出不一樣類型的數據元素,如直接put(Char char)、put(int value)等。當put時,其會將不一樣類型數據轉化爲字節類型從position位置開始依次存放,而當get時,其會根據不一樣的get類型,從position位置開始依次取出對應的字節數轉化後返回。
3、總結
本篇博文講解了NIO中的緩衝區,最核心的就是四個屬性,全部針對緩衝區的操做都是基於四個屬性的操做,讀者想要更具體的瞭解緩衝區的內容,能夠查閱源碼,謝謝各位園友的觀看~