Java?NIO?中Buffer?緩沖區(qū)解析
一、Buffer 簡介
Java NIO 中的 Buffer 用于和 NIO 通道進(jìn)行交互。數(shù)據(jù)是通道讀取到緩沖區(qū)
緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被包裝 NIO Buffer 對象,并且提供了一組方法,用來方便的訪問這塊內(nèi)存。緩沖區(qū)世紀(jì)上一個(gè)容器對象,更直接的說,其實(shí)就是一個(gè)數(shù)組,在 NIO 庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。 在讀取數(shù)據(jù)時(shí),它直接讀到緩沖區(qū)中;在寫數(shù)據(jù)時(shí),它也是寫入到緩沖區(qū)中的;任何時(shí)候訪問 NIO 中的數(shù)據(jù),都是將它放到緩沖區(qū)中。而在面向流 I/O 系統(tǒng)中,所有數(shù)據(jù)都是直接寫入或者直接將數(shù)據(jù)讀取到 Stream 對象中。
在 NIO 中,所有的緩沖區(qū)類型都是繼承于抽象類 Buffer, 最常用的就是 ByteBuffer. 對于 Java 中國呢的基本類型,金額都有一個(gè)具體 Buffer 類型與之對應(yīng),他們之間的繼承關(guān)系如下圖所示:
二、Buffer 的基本方法
1、使用 Buffer 讀寫數(shù)據(jù)
使用 Buffer 讀寫數(shù)據(jù),一般遵循以下四個(gè)步驟:
(1)寫數(shù)據(jù)到 Buffer
(2)調(diào)用flip() 方法
(3)從 Buffer 中讀取數(shù)據(jù)
(4)調(diào)用 clear() 方法或者 compact() 方法
當(dāng)向 buffer 寫數(shù)據(jù)時(shí),buffer 會記錄下寫了多少數(shù)據(jù)。一旦要讀數(shù)據(jù),需要通過 flip() 方法將 buffer 從寫模式切換到讀模式。在讀模式下,可以讀取到之前寫入到 buffer 的所有數(shù)據(jù)。一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫入。有兩種方式能清空緩沖區(qū):調(diào)用 clear() 或者 compact() 方法。clear() 方法會清空整個(gè)緩沖區(qū)。compact() 方法只會清除已經(jīng)度過的數(shù)據(jù)。任何未讀取的數(shù)據(jù)都被移動到了緩沖區(qū)的起始處,新寫的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面。
2、使用 Buffer 的例子
@Test public void buffer01() throws IOException { ? ? // FileChannel ? ? String pathName = "/Users/zhengsh/sourcecode.io/zhengsh-vvip/nio/src/main/resources/01.txt"; ? ? RandomAccessFile accessFile = ? ? ? ? new RandomAccessFile(pathName, "rw"); ? ? FileChannel channel = accessFile.getChannel(); ? ? // 創(chuàng)建 buffer , 大小 ? ? ByteBuffer buffer = ByteBuffer.allocate(1024); ? ? // 讀 ? ? int bytesRead = channel.read(buffer); ? ? while (bytesRead != -1) { ? ? ? ? // read 模式 ? ? ? ? buffer.flip(); ? ? ? ? while (buffer.hasRemaining()) { ? ? ? ? ? ? System.out.println((char) buffer.get()); ? ? ? ? } ? ? ? ? buffer.clear(); ? ? ? ? channel.read(buffer); ? ? } ? ? accessFile.close(); } @Test public void buffer02() { ? ? // 創(chuàng)建 buffer ? ? IntBuffer buffer = IntBuffer.allocate(8); ? ? for (int i = 0; i < buffer.capacity(); i++) { ? ? ? ? int j = 2 * (i + 1); ? ? ? ? buffer.put(j); ? ? } ? ? // 重置緩沖區(qū) ? ? buffer.flip(); ? ? while (buffer.hasRemaining()) { ? ? ? ? int value = buffer.get(); ? ? ? ? System.out.println(value + " ?"); ? ? } }
三、Buffer 的 capactity、posittion 和limit
為了理解 Buffer 的工作原理,需要熟悉它的三個(gè)屬性:
- capacity
- ponstition
- limit
position
和 limit
的含義取決于 Buffer 處在讀模式還是寫模式。不管 buffer 處于什么模式,capactity 的含義總是一樣的。
這里有一個(gè)關(guān)于 capacity, postition 和 limit 在讀模式中的說明:
(1) capactiy
作為一個(gè)內(nèi)存塊,Buffer 有一個(gè)固定的大小值,也叫做 “capactiy” . 你只能往里面寫 capacity 個(gè) byte
long、 char等類型。一旦 buffer 滿了,需要將其清空(通過讀數(shù)據(jù)或者清除數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)往里寫數(shù)據(jù)。
(2) postition
1)寫數(shù)據(jù)到 Bufer 中時(shí),position 表示寫入數(shù)據(jù)的當(dāng)前位置,position 的初始值為 0 。當(dāng)一個(gè) byte,long,等數(shù)據(jù)寫入到 buffer 后,position 會向下移動到下一個(gè)可插入的元素的 buffer 但愿。position 最大可為 capacity -1 (因?yàn)?position 的初始值為 0)
2)讀數(shù)據(jù)到 Buffer 中時(shí),position 表示讀數(shù)據(jù)的當(dāng)前位置,如 position = 2 時(shí)表示已經(jīng)開始讀了 3 個(gè) byte, 或者從第三個(gè) byte 開始讀取,通過 ByteBuffer.flip() 切換到讀模式 position 會被重置為 0, 當(dāng) Buffer 從 position 讀入數(shù)據(jù)后,position 會下移到下一個(gè)可讀入的數(shù)據(jù) Buffer 單元。
(3) limit
1)寫數(shù)據(jù)時(shí), limit 表示可以對 Buffer 最多寫入多少個(gè)數(shù)據(jù)。寫模式下,limit 等于 Buffer 的 capactiy
2)讀數(shù)據(jù)時(shí), limit 表示 Buffer 里有多少可讀數(shù)據(jù)(not null 的數(shù)據(jù)),因此能讀取到之前寫入的所有數(shù)據(jù)(limit 被設(shè)置為已寫數(shù)據(jù)的數(shù)量,這個(gè)值在寫模式下就是 position)
四、Buffer 的類型
Java NIO 有一下 Buffer 的類型
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- LongBuffer
- ShortBuffer
這些 buffer 類型都代表了不同的數(shù)據(jù)類型。換句話說,就是可以通過 char ,short, int, long , float 或者 double 類型來操作緩沖區(qū)的字節(jié)。
五、Buffer 分配和寫數(shù)據(jù)
1、 Buffer 分配
想要獲取一個(gè) Buffer 對象首先要進(jìn)行分配。每一個(gè) Buffer 類都有一個(gè) allocate 方法。
下面是一個(gè)分配 48 字節(jié) capactiy 的 ByteBuffer 的例子。
ByteBuffer buf = ByteBuffer.alloacte(48);
這是分配一個(gè)可存儲 1024 個(gè)字符的 CharBuffer:
ByteBuffer buf = ByteBuffer.alloacte(1024);
2、向 buffer 中寫數(shù)據(jù)
寫數(shù)據(jù)到 Buffer 有兩種方式:
- (1)從 channel 寫到 Buffer
- (2)通過 Buffer 的 put 方法寫到 Buffer 里。
從 Channel 寫到 Buffer 的例子
int byteRead = channel.read(buf); // read into buffer
通過 put 方法寫入 buffer 的例子:
buf.put(100);
put 的方法有很多版本,允許你不同的方式把數(shù)據(jù)寫入到 buffer 中,例如,寫到一個(gè)指定的位置,或者把字節(jié)數(shù)組寫入到 Buffer .
3、flip() 方法
flip 方法將 Buffer 從寫模式切換到讀模式。調(diào)用 flip() 方法將會 position 設(shè)置為 0 , 并且將 limt 設(shè)置為之前 position 的值。換句話說,position 現(xiàn)在用于標(biāo)記讀的位置,limit 表示之前寫進(jìn)了多少個(gè) byte, char 等(現(xiàn)在能讀取多少個(gè) byte, char 等)。
六、從 Buffer 中讀取數(shù)據(jù)
從 Buffer 中讀取數(shù)據(jù)到 Channel 中:
(1) 從 Buffer 中讀取數(shù)據(jù)到 Channel
(2)使用 get 方法從 Buffer 中讀取數(shù)據(jù)
從 Buffer 中讀取數(shù)據(jù)到 Channnel 到例子:
// read form buffer into channel? int bytesWritten = inChannel.write(buf);
使用 get() 方法從 Buffer 中讀取數(shù)據(jù)的例子:
byte aByte = buf.get();
get 方法中有很多版本,允許你以不同的方式 Buffer 中讀取數(shù)據(jù)。例如,從指定 position 讀取,或者從 Buffer 中讀取到字節(jié)數(shù)組。
七、Buffer 幾個(gè)方法
1、rewind() 方法
Buffer.rewind() 將 position 返回0, 所以你可以重讀 Buffer 中的所有數(shù)據(jù)。limit 保持不變,仍然表示能從 Buffer 中讀取到多少個(gè)元素(byte, char 等)。
2、clear() 與 compact() 方法
一旦讀完 Buffer 中的數(shù)據(jù),需要讓 Buffer 準(zhǔn)備好再次被寫入??梢酝ㄟ^ clear() 或 compact() 方法來完成
如果調(diào)用的是 cleanr () 方法,position 兼?zhèn)湓O(shè)置為 0 , limit 被設(shè)置成 capactiy 的值。換句話說,Buffer 被清空了。 Buffer 中的數(shù)據(jù)并未清除,只是這些標(biāo)記高數(shù)我們從哪里開始往 Buffer 中寫數(shù)據(jù)。
如果 Buffer 中有些數(shù)未讀的數(shù)據(jù),調(diào)用 clear() 方法,數(shù)據(jù)將 “被遺忘”,意味著不在有任何標(biāo)記會告訴你那些數(shù)據(jù)被讀過,那些還沒有。
如果 Buffer 中依然有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù),但是此時(shí)想要先寫這些數(shù)據(jù),那么使用 compact() 方法。
compact() 方法將所有未讀的數(shù)據(jù)拷貝到 Buffer 起始處。然后將 position 設(shè)置到最后一個(gè)未讀元素正后面。 limit 屬性依然像 clear() 方法一樣。設(shè)置成 capacity. 現(xiàn)在 Buffer 準(zhǔn)備好寫數(shù)據(jù)了,但是不會覆蓋未讀的數(shù)據(jù)。
3、mark() 與 reset() 方法
通過調(diào)用 Buffer.mark() 方法,可以標(biāo)記 Buffer 中的一個(gè)特定 position . 之后可以通過調(diào)用 Buffer.reset() 方法恢復(fù)到這個(gè) position 例如:
buffer.mark(); // call buffer.get() a couple of times, e.g. during parsing ? buffer.reset(); // set position back to mark
八、緩沖區(qū)操作
1、緩沖區(qū)分片
在 NIO 中除了可以分配或者包裝一個(gè)緩沖區(qū)對象外,還可以更具現(xiàn)有的緩沖區(qū)對象來創(chuàng)建一個(gè)子緩沖區(qū),即現(xiàn)有緩沖區(qū)上切出一片來作為一個(gè)新的緩沖區(qū),但現(xiàn)有的緩沖區(qū)與創(chuàng)建的子緩沖區(qū)在底層數(shù)組層面上是數(shù)據(jù)共享的,也就是說,子緩沖區(qū)相當(dāng)于是現(xiàn)有緩沖區(qū)的一個(gè)視圖窗口。調(diào)用 slice() 方法可以創(chuàng)建一個(gè)子緩沖區(qū)。
// 緩沖區(qū)分片 @Test public void b01() { ? ? ByteBuffer buffer = ByteBuffer.allocate(10); ? ? // 放入數(shù)據(jù) ? ? for (int i = 0; i < buffer.capacity(); i++) { ? ? ? ? buffer.put((byte) i); ? ? } ? ? // 創(chuàng)建子緩沖區(qū) ? ? buffer.position(3); ? ? buffer.limit(7); ? ? ByteBuffer slice = buffer.slice(); ? ? // 改變子緩沖區(qū)中的內(nèi)容 ? ? for (int i = 0; i < slice.capacity(); i++) { ? ? ? ? byte b = slice.get(i); ? ? ? ? b *= 10; ? ? ? ? slice.put(i, b); ? ? } ? ? // 復(fù)位 ? ? buffer.position(0); ? ? buffer.limit(buffer.capacity()); ? ? while (buffer.remaining() > 0) { ? ? ? ? System.out.println(buffer.get()); ? ? } }
輸出結(jié)果如下:
2、只讀緩沖區(qū)
只讀緩沖區(qū)非常簡單,可以讀取他們,但是不能向他們寫入數(shù)據(jù)??梢酝ㄟ^調(diào)用緩沖區(qū)的 asReadOnlyBufer() 方法,將任何常規(guī)緩沖區(qū)轉(zhuǎn)換為只讀緩沖區(qū),這個(gè)方法返回一個(gè)與原緩沖區(qū)完全相同的緩沖區(qū),并與原緩沖區(qū)共享數(shù)據(jù),只不過它是只讀的。如果原緩沖區(qū)的內(nèi)容發(fā)生了變化,只讀緩沖區(qū)的內(nèi)容隨之發(fā)生變化:
// 只讀緩沖區(qū) @Test public void b02() { ? ? ByteBuffer buffer = ByteBuffer.allocate(10); ? ? // 放入數(shù)據(jù) ? ? for (int i = 0; i < buffer.capacity(); i++) { ? ? ? ? buffer.put((byte) i); ? ? } ? ? // 創(chuàng)建一個(gè)只讀緩沖區(qū) ? ? ByteBuffer readOnlyBuf = buffer.asReadOnlyBuffer(); ? ? for (int i = 0; i < buffer.capacity(); i++) { ? ? ? ? byte b = buffer.get(i); ? ? ? ? b *= 10; ? ? ? ? buffer.put(i, b); ? ? } ? ? readOnlyBuf.position(0); ? ? readOnlyBuf.limit(readOnlyBuf.capacity()); ? ? while (readOnlyBuf.remaining() > 0) { ? ? ? ? System.out.println(readOnlyBuf.get()); ? ? } }
3、直接緩沖區(qū)
直接緩沖區(qū)為了加快 I/O 速度,使用一種特殊的方式為其分配內(nèi)存的緩沖區(qū), JDK 文檔的描述為:給定一個(gè)直接字節(jié)緩沖區(qū),Java 虛擬機(jī)將盡最大努力直接對它執(zhí)行本機(jī) I/O 操作之前(或之后),嘗試避免將緩沖區(qū)的內(nèi)容拷貝到一個(gè)中間緩沖區(qū)中或者從一個(gè)中間緩沖區(qū)中拷貝數(shù)據(jù)。要分配直接緩沖區(qū),需要調(diào) allocatieDirect() 方法,而不是 alloacte() 方法,使用方式與普通緩沖區(qū)并無區(qū)別。
// 直接緩沖區(qū),文件拷貝 @Test public void b03() throws IOException { ? ? String filePath = "/xx/01.txt"; ? ? FileInputStream inputStream = new FileInputStream(filePath); ? ? FileChannel fileInChannel = inputStream.getChannel(); ? ? String outPath = "/xx/02.txt"; ? ? FileOutputStream outputStream = new FileOutputStream(outPath); ? ? FileChannel fileOutChannel1 = outputStream.getChannel(); ? ? // ?使用 allocateDirect , 而不是 allocate ? ? ByteBuffer buffer = ByteBuffer.allocateDirect(1024); ? ? while (true) { ? ? ? ? buffer.clear(); ? ? ? ? int r = fileInChannel.read(buffer); ? ? ? ? if (r == -1) { ? ? ? ? ? ? break; ? ? ? ? } ? ? ? ? buffer.flip(); ? ? ? ? fileOutChannel1.write(buffer); ? ? } ? ? fileInChannel.close(); ? ? fileOutChannel1.close(); }
4、內(nèi)存映射文件 I/O
內(nèi)存映射文件 I/O 是一種讀和寫文件數(shù)據(jù)的方法,它可以比常規(guī)的基于流或者通道的 I/O 快得多。內(nèi)存映射 I/O 是通過使文件中的數(shù)據(jù)出現(xiàn)為內(nèi)存數(shù)組的內(nèi)容來完成的,這起初聽起來師傅不過就是為了將整個(gè)文件讀取到內(nèi)容中,但是事實(shí)上并不是這樣的。一般來說只有文件實(shí)際讀取或者寫入的部分才會映射到內(nèi)存中。
// 內(nèi)存映射文件 I/O? @Test public void b04() throws IOException { ? ? String filePath = "/xxx/01.txt"; ? ? RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "rw"); ? ? FileChannel fileChannel = randomAccessFile.getChannel(); ? ? MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024); ? ? mappedByteBuffer.put(0, (byte) 97); ? ? mappedByteBuffer.put(1023, (byte) 122); ? ? fileChannel.close(); }
到此這篇關(guān)于Java NIO 中Buffer 緩沖區(qū)解析的文章就介紹到這了,更多相關(guān)Java NIO 中Buffer 緩沖區(qū)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
servlet之ServletContext簡介_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了servlet之ServletContext簡介,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07mybatis動態(tài)拼接實(shí)現(xiàn)有條件的插入
這篇文章主要介紹了mybatis動態(tài)拼接實(shí)現(xiàn)有條件的插入,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02MyBatis-Plus QueryWrapper及LambdaQueryWrapper的使用詳解
這篇文章主要介紹了MyBatis-Plus QueryWrapper及LambdaQueryWrapper的使用詳解,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Spring-Bean創(chuàng)建對象的步驟方式詳解
在本篇文章里小編給大家分享的是關(guān)于Spring-Bean創(chuàng)建對象的步驟方式詳解內(nèi)容,有興趣的朋友們跟著學(xué)習(xí)下。2020-02-02SpringBoot mybatis 實(shí)現(xiàn)多級樹形菜單的示例代碼
這篇文章主要介紹了SpringBoot mybatis 實(shí)現(xiàn)多級樹形菜單的示例代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05