Java NIO学习系列一:Buffer
從本文開始我會開始總結NIO部分,Java NIO(注意,這里的NIO其實叫New IO)是用來替換標準Java IO以及Java 網絡API的,其提供了一系列不同與標準IO API的方式來處理IO,從JDK1.4開始引入,其目的在于提高速度。
之所以能夠提高速度是因為其所使用的結構更接近于操作系統執行I/O的方式:通道和緩沖器。我們可以把它想象成一個煤礦,通道是一個包含煤層(數據)的礦藏,而緩沖器則是派送到礦藏的卡車。卡車滿載煤炭而歸,我們再從卡車上獲得煤炭。也就是說,我們并沒有直接和通道交互,而是和緩沖器交互,并把緩沖器派送到通道。通道要么從緩沖器獲得數據,要么向緩沖器發送數據。
? 在標準IO的API中,使用字節流和字符流。而在Java NIO中是使用Channel(通道)和Buffer(緩沖區),數據從channel中讀取到buffer中,或從buffer寫入到channel中。Java NIO類庫中的核心組件為:
- Buffer
- Channel
- Selector
本文中我們會著重總結Buffer相關的知識點(后面的文章中會繼續介紹Channel即Selector),本文主要會圍繞如下幾個方面展開:
Buffer簡介
Buffer的內部結構
Buffer的主要API
ByteBuffer
Buffer類型
總結
?
1.?Buffer簡介
Java NIO中的Buffer一般和Channel配對使用。可以從Channel中讀取數據到Buffer,或者寫數據到Channel中。一個Buffer其實就是代表一個內存塊,你可以往里面寫數據或者從中讀取數據。這個內存塊被包裝成一個Buffer對象,并且提供了一系列方法使得操作內存塊更便捷。
通過Buffer來讀寫數據通常包括如下4步:
當往Buffer中寫數據時,Buffer能夠記錄寫了多少數據。當要從Buffer中讀取數據時,就需要通過調用flip()方法將Buffer從寫模式切換到讀模式。一旦讀完所有數據,需要清空Buffer,讓它再次處于寫狀態。可以通過調用clear()或compact()方法來完成這一步:
- clear()方法會清空整個Buffer;
- compact()方法僅僅清空你已經從Buffer中讀取的數據,未讀數據會被移動到Buffer起始位置,可以緊接著未讀的數據寫入新的數據;
??如下是一個簡單的使用例子,通過FileChannel和ByteBuffer讀取pom.xml文件,并逐字節輸出:
public class BufferDemo {public static void main(String[] args) {try {RandomAccessFile raf = new RandomAccessFile("pom.xml","r");FileChannel channel = raf.getChannel();ByteBuffer buffer = ByteBuffer.allocate(48);int byteReaded = channel.read(buffer);while(byteReaded != -1) {buffer.flip();while(buffer.hasRemaining()) {System.out.print((char)buffer.get());}buffer.clear();byteReaded = channel.read(buffer);}raf.close();}catch (Exception e) {e.printStackTrace();}} }?
2.?Buffer的內部結構
上面說到Buffer封裝了一塊內存塊,并提供了一系列的方法使得可以方便地操縱內存中的數據。至于如何操縱?Buffer提供了4個索引。要理解Buffer的工作原理,就需要從這些索引說起:
- capacity(容量);
- position(位置);
- limit(界限);
- mark(標記);
? 其中position和limit的含義取決于Buffer是處于什么模式(讀或者寫模式),capacity的含義則和模式無關,而mark則只是一個標記,可以通過mark()方法進行設置。下圖描述了讀寫模式下三種屬性分別代表的含義,詳細解釋見下文:
2.1 Capacity
Buffer代表一個內存塊,所以其是有確定大小的,也叫“容量”。可以往buffer中寫入各種數據如byte、long、chars等,當Buffer被寫滿了則需要將其清空(可以通過讀取數據或者清空數據)之后才能繼續寫入數據。
2.2 Position
當往Buffer中寫數據時,寫入的地方就是所謂的position,其初始值為0,最大值為capacity-1。當往Buffer中寫入一個byte或者long的數據時,position會前移以指向下一個即將被插入的位置。
當從Buffer中讀取數據時,讀取數據的地方就是所謂的position。當執行flip將Buffer從寫模式切換到讀模式時,position會被重置為0。隨著不斷從Buffer讀取數據,position也會不斷后移指向下一個將被讀取的數據。
2.3 Limit
在寫模式下,Buffer的limit是指能夠往Buffer中寫入多少數據,其值等于Buffer的capacity。
在讀模式下,Buffer的limit是指能夠從Buffer讀取多少數據出來。因此當從寫模式切換到讀模式下時,limit就被設置為寫模式下的position的值(這很好理解,寫了多少才能讀到多少)。
?2.4 Mark
mark其實就是一個標記,可以通過mark()方法設置,設置值為當前的position。
?
下面是用于設置和復位索引以及查詢它們值的方法:
?
capacity() ? 返回緩沖區容量
clear() 清空緩沖區,將position設置為0,limit設置為容量。我們可以調用此方法覆寫緩沖區
flip() 將limit設置為position,position設置為0。此方法用于準備從緩沖區讀取已經寫入的數據
limit() ? 返回limit值
limit(int lim) 設置limit值
mark() ?將mark設置為position
position() 返回position值
position(int pos) 設置position值
remaining() 返回(limit - position)
hasRemaining() 若有介于position和limit之間的元素,則返回true
?
3.?Buffer的主要API
除了如上和索引相關的方法之外,Buffer還提供了一些其他的方法用于寫入、讀取等操作。
3.1 給Buffer分配空間
要獲得一個Buffer對象就可以通過Buffer類的allocate()方法來實現,如下分別是分配一個48字節的ByteBuffer和1024字符的CharBuffer:
ByteBuffer buf = ByteBuffer.allocate(48); CharBuffer buf = CharBuffer.allocate(1024);3.2 往Buffer中寫數據
有兩種方式往Buffer中寫入數據:
- 從Channel中往Buffer寫數據;
- 通過Buffer的put()方法寫入數據;
put()方法有多個重載版本,比如從指定位置寫入數據,或寫入字節數組等。
3.3 flip()
flip()方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設為0,limit設為position之前的值。
3.4?從Buffer讀數據
也有兩種方法從Buffer讀取數據:
- 從Buffer中讀數據到Channel中;
- 調用Buffer的get()方法讀取數據;
3.5 rewind()
rewind()方法將position設置為0,可以從頭開始讀數據。
3.6?clear()和compact()
當從Buffer讀取數據結束之后要將其切換回寫模式,可以調用clear()、compact()這兩個方法,兩者之間的區別如下:
調用clear(),會將position設為0,limit設為capacity,也就是說Buffer被清空了,但是里面的數據仍然存在,只是這時沒有標記可以告訴你哪些數據是已讀,哪些是未讀。
如果讀取到一半需要寫入數據,但是未讀的數據稍后還需要讀取,這時可以使用compact(),其會將所有未讀取的數據復制到Buffer的前面,將position設置到這些數據后面,limit設置為capacity,所以此時是從未讀的數據后面開始寫入新的數據。
3.7?mark()和reset()
調用mark()方法可以標志一個指定的位置(即設置mark值),之后調用reset()方法時position又會回到之前標記的位置。
?
4.?ByteBuffer
? ByteBuffer是一個比較基礎的緩沖器,繼承自Buffer,是可以存儲未加工字節的緩沖器,并且也是唯一直接與通道交互的緩沖器。可以通過ByteBuffer的allocate()方法來分配一個固定大小的ByteBuffer,并且其還有一個方法選擇集,用于以原始的字節形式或基本類型輸出和讀取數據。但是,沒辦法輸出或讀取對象,即使是字符串對象也不行。這種處理雖然很低級,但卻正好,因為這是大多數操作系統中更有效的映射方式。
ByteBuffer也分為直接和非直接緩沖器,通過allocate()創建的就是非直接緩沖器,而通過allocateDirect()方法就可以創建出一個緩沖器直接緩沖器,這是一個與操作系統有更高耦合性的緩沖器,也就意味著它能夠帶來更高的速度,但是分配的開支也會更大。
盡管ByteBuffer只能保存字節類型的數據,但是它具有可以從其所容納的字節中產生出各種不同基本類型值的方法。下面的例子展示怎樣使用這些方法來插入和抽取各種數值:
public class GetData { private static final int BSIZE = 1024;public static void main(String[] args){ByteBuffer bb = ByteBuffer.allocate(BSIZE);int i = 0;while(i++ < bb.limit())if(bb.get() != 0)System.out.println("nonzero");System.out.println("i = " + i);bb.rewind();// store and read a char array:bb.asCharBuffer().put("Howdy!");char c;while((c = bb.getChar()) != 0)System.out.print(c + " ");System.out.println();bb.rewind();// store and read a short:bb.asShortBuffer().put((short)471142);System.out.println(bb.getShort());bb.rewind();// sotre and read an int:bb.asIntBuffer().put(99471142);System.out.println(bb.getInt());bb.rewind();// store and read a long:bb.asLongBuffer().put(99471142);System.out.println(bb.getLong());bb.rewind();// store and read a float:bb.asFloatBuffer().put(99471142);System.out.println(bb.getFloat());bb.rewind();// store and read a double:bb.asDoubleBuffer().put(99471142);System.out.println(bb.getDouble());bb.rewind();} }?
5.?Buffer類型
Java NIO中包含了如下幾種Buffer:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
這些Buffer類型代表著不同的數據類型,使得可以通過Buffer直接操作如char、short等類型的數據而不是字節數據。其中MappedByteBuffer略有不同,后面會專門總結。
通過ByteBuffer我們只能往Buffer直接寫入或者讀取字節數組,但是通過對應類型的Buffer比如CharBuffer、DoubleBuffer等我們可以直接往Buffer寫入char、double等類型的數據。或者利用ByteBuffer的asCharBuffer()、asShorBuffer()等方法獲取其視圖,然后再使用其put()方法即可直接寫入基本數據類型,就像上面的例子。
這就是視圖緩沖器(view buffer)可以讓我們通過某個特定的基本數據類型的視窗查看其底層的ByteBuffer。ByteBuffer依然是實際存儲數據的地方,“支持”著前面的視圖,因此對視圖的任何修改都會映射成為對ByteBuffer中數據的修改。這使得我們可以很方便地向ByteBuffer插入數據。視圖還允許我們從ByteBuffer一次一個地(與ByteBuffer所支持的方式相同)或者成批地(通過放入數組中)讀取基本類型值。在下面的例子中,通過IntBuffer操縱ByteBuffer中的int型數據:
public class IntBufferDemo { private static final int BSIZE = 1024;public static void main(String[] args){ByteBuffer bb = ByteBuffer.allocate(BSIZE);IntBuffer ib = bb.asIntBuffer();// store an array of int:ib.put(new int[]{11,42,47,99,143,811,1016});// absolute location read and write:System.out.println(ib.get(3));ib.put(3,1811);// setting a new limit before rewinding the buffer.ib.flip();while(ib.hasRemaining()){int i = ib.get();System.out.println(i);}} }上例中先用重載后的put()方法存儲一個整數數組。接著get()和put()方法調用直接訪問底層ByteBuffer中的某個整數位置。這些通過直接與ByteBuffer對話訪問絕對位置的方式也同樣適用于基本類型。
?
6.?總結
本文簡單總結了Java NIO(Java New IO),其目的在于提高速度。Java NIO類庫中主要包括Buffer、Channel、Selector,本文主要總結了Buffer相關的知識點:
- Buffer叫緩沖器,她是和Channel(通道)交互的,可以從channel中讀數據到buffer中,或者從buffer往channel中寫數據;
- Buffer內部封裝了一塊內存,提供了一系列API使得可以方便地操作內存中的數據。其內部是通過capacity、position、limit、mark等變量來跟蹤標記封裝的數據的;
- ByteBuffer是最基本的Buffer,是唯一可以直接與通道交互的緩沖器,其可以直接操縱字節數據或字節數組;
- 除了ByteBuffer之外,Buffer還有許多別的類型如:MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer;
- 雖然只有ByteBuffer能夠直接和通道交互,但是可以從ByteBuffer獲取多種不同的視圖緩沖器,進而同時具備了直接操作基本數據類型和與通道交互的能力;
總結
以上是生活随笔為你收集整理的Java NIO学习系列一:Buffer的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java I/O系统学习系列三:I/O流
- 下一篇: Java NIO学习系列二:Channe