【基礎篇】netty源碼死磕1.2: html
NIO Bufferjava
Buffer是一個抽象類,位於java.nio包中,主要用做緩衝區。Buffer緩衝區本質上是一塊能夠寫入數據,而後能夠從中讀取數據的內存。這塊內存被包裝成NIO Buffer對象,並提供了一組方法,用來方便的訪問該塊內存。編程
注意:Buffer是非線程安全類。緩存
Buffer在內部也是利用byte[]做爲內存緩衝區,只不過多提供了一些標記變量屬性而已。當多線程訪問的時候,能夠清楚的知道當前數據的位置。安全
有三個重要的標記屬性:capacity、position、limit。多線程
除此以外,還有一個標記屬性:mark,能夠臨時保持一個特定的position,須要的時候,能夠恢復到這個位置。app
做爲一個內存塊,Buffer有一個固定的大小值,也叫「capacity」。你只能往裏寫capacity個數據。一旦Buffer滿了,就不能再寫入。post
capacity與緩存的數據類型相關。指的不是內存的字節的數量,而是寫入的對象的數量。好比使用的是一個保存double類型的Buffer(DoubleBuffer),寫入的數據是double類型, 若是其 capacity 是100,那麼咱們最多能夠寫入100個 double 數據.學習
capacity一旦初始化,就不能不會改變。大數據
緣由是什麼呢?
Buffer對象在初始化時,會按照capacity分配內部的內存。內存分配好後,大小就不能變了。分配內存時,通常使用Buffer的抽象子類ByteBuffer.allocate()方法,其實是生成ByteArrayBuffer類。
position表示當前的位置。position在Buffer的兩種模式下的值是不一樣的。
讀模式下的position的值爲:
當讀取數據時,也是從position位置開始讀。當將Buffer從寫模式切換到讀模式,position會被重置爲0。當從Buffer的position處讀取數據時,position向前移動到下一個可讀的位置。
寫模式下的position的值爲:
在寫模式下,當寫數據到Buffer中時,position表示當前的寫入位置。初始的position值爲0,position最大可爲capacity – 1。
每當一個數據(byte、long等)寫到Buffer後, position會向後移動到下一個可插入數據的可寫的位置。
limit表示最大的限制。在Buffer的兩種模式下,limit的值是不一樣的。
讀模式下的limit的值爲:
讀模式下,Buffer的limit表示最多能從Buffer裏讀多少數據。當Buffer從寫切換到讀模式時,limit的值,設置成寫模式的position 值,也是是寫模式下以前寫入的數量值。
舉一個簡單的例子,說明一下讀模式下的limit值:
先向Buffer寫數據,Buffer在寫模式。每寫入一個數據,position向後面移動一個位置,值加一。假定寫入了5個數,當寫入完成後,position的值爲5。這時,就能夠讀取數據了。當開始讀取數據時,Buffer切換到讀模式。limit的值,先會被設置成寫入數據時的position值。這裏是5,表示能夠讀取的最大限制是5個數。
寫模式下的limit的值爲:
limit表示表示能夠寫入的數據最大限制。在切換成寫模式時,limit的值會被更改,設置成Buffer的capacity,爲Buffer的容量。
在Buffer的四個屬性之間,有一個簡單的數量關係,以下:
capacity>=limit>=position>=mark>=0
用一個表格,對着4個屬性的進行一下對比:
屬性 |
描述 |
capacity |
容量,便可以容納的最大數據量;在緩衝區建立時被設定而且不能改變 |
limit |
上界,緩衝區中當前數據量 |
position |
位置,下一個要被讀或寫的元素的索引 |
mark(位置標記) |
調用mark(pos)來設置mark=pos,再調用reset()可讓position恢復到標記的位置即position=mark |
在NIO中主要有八種緩衝區類,分別以下:
ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
MappedByteBuffer
這些 Buffer 覆蓋了能從 IO 中傳輸的全部的 Java 基本數據類型。其中MappedByteBuffer是專門用於內存映射的一種ByteBuffer)。
本節結合Buffer的幾個方法,作了一個完整的實例,包含了從Buffer實例的獲取、寫入、讀取、重複讀、標記和重置等一個系列操做的完整流程。
爲了獲取一個 Buffer 對象,咱們首先須要分配內存空間。分配內存空間使用allocate()方法。
public static void allocatTest() { byteBuffer = IntBuffer.allocate(20); Logger.info("------------after allocate------------------"); Logger.info("position=" + byteBuffer.position()); Logger.info("limit=" + byteBuffer.limit()); Logger.info("capacity=" + byteBuffer.capacity()); } byteBuffer = IntBuffer.allocate(20);
這裏咱們分配了20* sizeof(int)字節的內存空間.
輸出的結果以下:
main |> 分配內存 allocatTest |> ------------after allocate------------------ allocatTest |> position=0 allocatTest |> limit=20 allocatTest |> capacity=20
經過結果,能夠看到Buffer屬性的值。
調用allocate分配內存後,buffer處於寫模式。能夠經過buffer的put方法寫入數據。put方法有一個要求,須要寫入的數據類型與Buffer的類型一致。
接着前面的例子,繼續上寫入的實例代碼:
public static void putTest()
{
for (int i = 0; i < 5; i++)
{
byteBuffer.put(i);
}
Logger.info("------------after put------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
寫入5個元素後,輸出的結果爲:
main |> 寫入 putTest |> ------------after putTest------------------ putTest |> position=5 putTest |> limit=20 putTest |> capacity=20
調用了put方法後,buffer處於寫模式。寫入5個數據後,能夠看到,position 變成了5,指向了第6個能夠寫入的元素位置。
除了在新建的buffer以後,如何將buffer切換成寫模式呢?
調用 Buffer.clear() 清空或 Buffer.compact()壓縮方法,能夠將 Buffer 轉換爲寫模式。
put方法寫入數據以後,能夠直接從buffer中讀嗎?
呵呵,不能。
還須要調用filp()走一個轉換的工做。flip()方法是Buffer的一個模式轉變的重要方法。簡單的說,是寫模式翻轉成讀模式——寫轉讀。
接着前面的例子,繼續上flip()方法的例子代碼:
public static void flipTest() { byteBuffer.flip(); Logger.info("------------after flip ------------------"); Logger.info("position=" + byteBuffer.position()); Logger.info("limit=" + byteBuffer.limit()); Logger.info("capacity=" + byteBuffer.capacity()); }
接着上一步的寫入,在調用flip以後,buffer的屬性有一些奇妙的變化。
運行上面的程序,輸出以下:
main |> 翻轉 flipTest |> ------------after flipTest ------------------ flipTest |> position=0 flipTest |> limit=5 flipTest |> capacity=20
注意到沒有,position從前一個小節的5,變成了0。而limit的保存了以前的position,從20變成5。
這是爲何呢? 先看其源碼,Buffer.flip()方法的源碼以下:
public final Buffer flip() { limit = position; position = 0; mark = UNSET_MARK; return this; }
解釋一下啊,flip()方法主要是從讀模式切換成寫模式,調整的規則是:
(1)首先設置可讀的長度limit。將寫模式下的Buffer中內容的最後位置position值變爲讀模式下的limit位置值,新的limit值做爲讀越界位置;
(2)其次設置讀的起始位置。將當position值置爲0,表示從0位置開始讀。轉換後重頭開始讀。
(3)若是以前有mark保存的標記位置,還要消除。由於那是寫模式下的mark標記。
get()讀數據很簡單,每次從postion的位置讀取一個數據,而且進行相應的buffer屬性的調整。
接着前面的例子,繼續上讀取buffer的例子代碼:
public static void getTest() { Logger.info("------------after &getTest 2------------------"); for (int i = 0; i < 2; i++) { int j = byteBuffer.get(); Logger.info("j = " + j); } Logger.info("position=" + byteBuffer.position()); Logger.info("limit=" + byteBuffer.limit()); Logger.info("capacity=" + byteBuffer.capacity()); Logger.info("------------after &getTest 3------------------"); for (int i = 0; i < 3; i++) { int j = byteBuffer.get(); Logger.info("j = " + j); } Logger.info("position=" + byteBuffer.position()); Logger.info("limit=" + byteBuffer.limit()); Logger.info("capacity=" + byteBuffer.capacity()); }
先讀2個,再讀3個,輸出的Buffer屬性值以下:
main |> 讀取 getTest |> ------------after &getTest 2------------------ getTest |> j = 0 getTest |> j = 1 getTest |> position=2 getTest |> limit=5 getTest |> capacity=20 getTest |> ------------after &getTest 3------------------ getTest |> j = 2 getTest |> j = 3 getTest |> j = 4 getTest |> position=5 getTest |> limit=5 getTest |> capacity=20
讀完以後,緩存的position 值變成了一個沒有數據的元素位置,和limit的值相等,已經不能在讀了。
讀完以後,是否能夠直接寫數據呢?
不能。一旦讀取了全部的 Buffer 數據,那麼咱們必須清理 Buffer,讓其從新可寫,能夠調用 Buffer.clear() 或 Buffer.compact()。
已經讀完的數據,須要再讀一遍,能夠直接使用get方法嗎?
答案是,不能。怎麼辦呢?
使用rewind() 方法,能夠進重複讀的設置。rewind()也叫倒帶,就像播放磁帶同樣,倒回去,從新播放。
接着前面的例子,繼續上重複讀的例子代碼:
public static void rewindTest() { byteBuffer.rewind(); Logger.info("------------after flipTest ------------------"); Logger.info("position=" + byteBuffer.position()); Logger.info("limit=" + byteBuffer.limit()); Logger.info("capacity=" + byteBuffer.capacity()); }
實例的結果以下:
main |> 重複讀 rewindTest |> ------------after flipTest ------------------ rewindTest |> position=0 rewindTest |> limit=5 rewindTest |> capacity=20
flip()方法主要是調整Buffer的 position 屬性,調整的規則是:
(1)position設回0,因此你能夠重讀Buffer中的全部數據;
(2)limit保持不變,數據量仍是同樣的,仍然表示能從Buffer中讀取多少個元素。
Buffer.rewind()方法的源碼以下:
public final Buffer rewind() { position = 0; mark = -1; return this; }
看到了實現的源碼應該就會清楚flip()的做用了。rewind()方法與flip()很類似,區別在於rewind()不會影響limit,而flip()會重設limit屬性值。
Buffer.mark()方法將當前的 position 的值保存起來,放在mark屬性中,讓mark屬性記住當前位置,以後能夠調用Buffer.reset()方法將 position 的值恢復回來。
Buffer.mark()和Buffer.reset()方法是一一配套使用的。都是須要操做mark屬性。
在重複讀的實例代碼中,讀到第3個元素,使用mark()方法,設置一下mark 屬性,保存爲第3個元素的位置。
下面上實例,演示一下mark和reset的結合使用。
實例繼續接着上面的rewind倒帶後的buffer 狀態,開始reRead重複讀,實例代碼以下:
public static void reRead()
{
Logger.info("------------after reRead------------------");
for (int i = 0; i < 5; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);
if (i == 2)
{
byteBuffer.mark();
}
}
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
而後接着上一段reset()實例代碼,以下:
public static void afterReset() { Logger.info("------------after reset------------------"); byteBuffer.reset(); Logger.info("position=" + byteBuffer.position()); Logger.info("limit=" + byteBuffer.limit()); Logger.info("capacity=" + byteBuffer.capacity()); }
上面咱們調用 mark() 方法將當前的 position 保存起來(在讀模式,所以保存的是讀的 position)。接着使用 reset() 恢復原來的讀 position,所以讀 position 就爲3,能夠再次開始從第2個元素讀取數據.
輸出的結果是:
afterReset |> ------------after reset------------------ afterReset |> position=3 afterReset |> limit=5 afterReset |> capacity=20
調用reset以後,position的值爲3,表示能夠從第三個元素開始讀。
Buffer.mark()和Buffer.reset()其實很簡答,其源碼以下:
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
clear()方法的做用有兩種:
(1)寫模式下,當一個 buffer 已經寫滿數據時,調用 clear()方法,切換成讀模式,能夠從頭讀取 buffer 的數據;
(2)讀模式下,調用 clear()方法,將buffer切換爲寫模式,將postion爲清零,limit設置爲capacity最大容量值,能夠一直寫入,直到buffer寫滿。
接着上面的實例,使用實例代碼,演示一下clear方法。
代碼以下:
public static void clearDemo() { Logger.info("------------after clear------------------"); byteBuffer.clear(); Logger.info("position=" + byteBuffer.position()); Logger.info("limit=" + byteBuffer.limit()); Logger.info("capacity=" + byteBuffer.capacity()); }
運行以後,結果以下:
main |> 清空 clearDemo |> ------------after clear------------------ clearDemo |> position=0 clearDemo |> limit=20 clearDemo |> capacity=20
在clear()以前,buffer是在讀模式下。clear()以後,能夠看到,清空了position 的值,設置爲起始位置。
clear 方法源碼:
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
根據源碼咱們能夠知道,clear 將 positin 設置爲0,將 limit 設置爲 capacity。
總結一下,使用 NIO Buffer 的步驟以下:
一:將數據寫入到 Buffer 中;
二:調用 Buffer.flip()方法,將 NIO Buffer 轉換爲讀模式;
三:從 Buffer 中讀取數據;
四:調用 Buffer.clear() 或 Buffer.compact()方法,將 Buffer 轉換爲寫模式。
當咱們將數據寫入到 Buffer 中時,Buffer 會記錄咱們已經寫了多少的數據;當咱們須要從 Buffer 中讀取數據時,必須調用 Buffer.flip()將 Buffer 切換爲讀模式。
package com.crazymakercircle.iodemo.base;
import com.crazymakercircle.util.Logger;
import java.nio.IntBuffer;
public class BufferDemo
{
static IntBuffer byteBuffer = null;
public static void allocatTest()
{
byteBuffer = IntBuffer.allocate(20);
Logger.info("------------after allocate------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
public static void putTest()
{
for (int i = 0; i < 5; i++)
{
byteBuffer.put(i);
}
Logger.info("------------after putTest------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
public static void flipTest()
{
byteBuffer.flip();
Logger.info("------------after flipTest ------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
public static void rewindTest()
{
byteBuffer.rewind();
Logger.info("------------after flipTest ------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
public static void getTest()
{
Logger.info("------------after &getTest 2------------------");
for (int i = 0; i < 2; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);
}
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
Logger.info("------------after &getTest 3------------------");
for (int i = 0; i < 3; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);
}
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
public static void reRead()
{
Logger.info("------------after reRead------------------");
for (int i = 0; i < 5; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);
if (i == 2)
{
byteBuffer.mark();
}
}
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
public static void afterReset()
{
Logger.info("------------after reset------------------");
byteBuffer.reset();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
public static void clearDemo()
{
Logger.info("------------after clear------------------");
byteBuffer.clear();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
public static void main(String[] args)
{
Logger.info("分配內存");
allocatTest();
Logger.info("寫入");
putTest();
Logger.info("翻轉");
flipTest();
Logger.info("讀取");
getTest();
Logger.info("重複讀");
rewindTest();
reRead();
Logger.info("make&reset寫讀");
afterReset();
Logger.info("清空");
clearDemo();
}
}
代碼工程: JavaNioDemo.zip
下載地址:在瘋狂創客圈QQ羣文件共享。
瘋狂創客圈 Netty 死磕系列 10多篇深度文章: 【博客園 總入口】 QQ羣:104131248