Netty NIO之ByteBuffer類基礎學習
什么是 Netty?
Netty 是一個高性能、異步事件驅動的網絡應用程序框架,主要用于快速開發(fā)可維護、可擴展的高性能服務器和客戶端。Netty 提供了簡單易用的 API,支持多種協(xié)議和傳輸方式,并且有著高度靈活的擴展和自定義能力。
Netty 的設計目標是提供一種易于使用、高效、可擴展的異步 IO 網絡編程框架。它采用了 NIO(Non-blocking IO)的方式來進行網絡操作,避免了傳統(tǒng)的阻塞式 IO 常常面臨的性能瓶頸。同時,Netty 還提供了優(yōu)秀的線程模型和內存管理機制,保證了高并發(fā)下的穩(wěn)定性和性能。
通過 Netty,開發(fā)者可以方便地實現(xiàn)基于 TCP、UDP、HTTP、WebSocket 等多種協(xié)議的通信應用。同時,Netty 還提供了編解碼器、SSL 支持等組件,使得開發(fā)者可以更加專注于業(yè)務邏輯的實現(xiàn)。
什么是 ByteBuffer?
ByteBuffer 是 Java 中的一個類,它提供了一種方便的方式來處理原始字節(jié)數(shù)據(jù)。ByteBuffer 可以被看作是一個緩沖區(qū),它可以容納一定數(shù)量的字節(jié)數(shù)據(jù),并提供了一系列方法來操作這些數(shù)據(jù)。
使用 ByteBuffer,可以輕松地讀取和寫入二進制數(shù)據(jù)。它還提供了對不同類型數(shù)據(jù)的支持,如整數(shù)、浮點數(shù)等。ByteBuffer 還支持對數(shù)據(jù)進行切片,以及對緩沖區(qū)中的數(shù)據(jù)進行復制、壓縮、解壓等操作。
在 Java 中,ByteBuffer 通常用于處理 I/O 操作,例如從文件或網絡中讀取和寫入數(shù)據(jù)。它也可以用于處理加密和解密數(shù)據(jù),以及處理圖像和音頻文件等二進制數(shù)據(jù)??傊?,ByteBuffer 是 Java 中非常有用的一個類,可以幫助開發(fā)人員更輕松地處理二進制數(shù)據(jù)。
基本使用
- 向 buffer 寫入數(shù)據(jù),例如調用
channel.read(buffer)
; 調用
flip()
切換至讀模式;flip
會使得 buffer 中的 limit 變?yōu)?position,position 變?yōu)?0;
- 從 buffer 讀取數(shù)據(jù),例如調用
buffer.get()
; 調用
clear()
或者compact()
切換至寫模式;- 調用
clear()
方法時,position=0
,limit 變?yōu)?capacity; - 調用
compact()
方法時,會將緩沖區(qū)中的未讀數(shù)據(jù)壓縮到緩沖區(qū)前面;
- 調用
- 重復 1~4 的步驟;
編寫代碼進行測試:
@Slf4j public class TestByteBuffer { public static void main(String[] args) { try (FileChannel channel = new FileInputStream("data.txt").getChannel()) { // 準備緩沖區(qū) ByteBuffer buffer = ByteBuffer.allocate(10); while (true) { // 從 channel 讀取數(shù)據(jù)寫入到 buffer int len = channel.read(buffer); log.debug("讀取到的字節(jié)數(shù) {}", len); if (len == -1) break; // 打印 buffer 內容 buffer.flip(); // 切換至讀模式 while(buffer.hasRemaining()) { // 是否還有剩余未讀數(shù)據(jù) byte b = buffer.get(); log.debug("實際字節(jié) {}", (char)b); } buffer.clear(); } } catch (IOException e) { } } }
運行結果:
注意,日志需要進行配置,在 /src/main/resources/
路徑下,創(chuàng)建 logback.xml
,其中的內容如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns="http://ch.qos.logback/xml/ns/logback" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd"> <!-- 輸出控制,格式控制 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%date{HH:mm:ss} [%-5level] [%thread] %logger{17} - %m%n </pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 日志文件名稱 --> <file>logFile.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 每天產生一個新的日志文件 --> <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern> <!-- 保留 15 天的日志 --> <maxHistory>15</maxHistory> </rollingPolicy> <encoder> <pattern>%date{HH:mm:ss} [%-5level] [%thread] %logger{17} - %m%n </pattern> </encoder> </appender> <!-- 用來控制查看哪個類的日志內容(對 mybatis name 代表命名空間)--> <logger name="com.sidiot.netty" level="DEBUG" additivity="false"> <appender-ref ref="STDOUT" /> </logger> <root level="ERROR"> <appender-ref ref="STDOUT" /> </root> </configuration>
將末尾部分的 <logger name="com.sidiot.netty" level="DEBUG" additivity="false">
中的 name
的屬性值改成自己的包名即可。
部分讀者可能會遇到如下問題:
這是由于 lombok
引起的,需要檢查一下是否安裝了 lombok
的插件,以及是否是最新版的 lombok
,博主這里用的版本如下:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency>
內部結構
字節(jié)緩沖區(qū)的父類 Buffer 中有四個核心屬性,從以下源碼中可以清晰獲知:
// Invariants: mark <= position <= limit <= capacity private int mark = -1; private int position = 0; private int limit; private int capacity;
- position:表示當前緩沖區(qū)中下一個要被讀或寫的字節(jié)索引位置,默認值為 0。當我們調用
put()
方法往緩沖區(qū)中寫入數(shù)據(jù)時,position 會自動向后移動,指向下一個可寫的位置;當我們調用get()
方法從緩沖區(qū)中讀取數(shù)據(jù)時,position 也會自動向后移動,指向下一個可讀的位置。 - limit:表示當前緩沖區(qū)的限制大小,默認值為 capacity。在寫模式下,limit 表示緩沖區(qū)最多能夠寫入的字節(jié)數(shù);在讀模式下,limit 表示緩沖區(qū)最多能夠讀取的字節(jié)數(shù)。在一些場景下,我們可以通過設置 limit 來防止越界訪問緩沖區(qū)。
- capacity:表示緩沖區(qū)的容量大小,默認創(chuàng)建 Buffer 對象時指定。capacity 只能在創(chuàng)建緩沖區(qū)時指定,并且不能改變。例如,我們可以創(chuàng)建一個容量為 1024 字節(jié)的 Buffer 對象,然后往里面寫入不超過 1024 字節(jié)的數(shù)據(jù)。
- mark:mark 和 reset 方法一起使用,用于記錄和恢復 position 的值。在 ByteBuffer 中,我們可以通過調用
mark()
方法來記錄當前 position 的值,然后隨意移動 position,最后再通過調用reset()
方法將 position 恢復到 mark 記錄的位置。使用 mark 和 reset 可以在某些情況下提高代碼的效率,避免頻繁地重新計算或查詢某個值。
這些屬性一起組成了 Buffer 的狀態(tài),我們可以根據(jù)它們的值來確定當前緩沖區(qū)的狀態(tài)和可操作范圍。
初始化時,position
,limit
,capacity
的位置如下:
寫模式下,position
代表寫入位置,limit
代表寫入容量,寫入3個字節(jié)后的狀態(tài)如下圖所示:
當使用 flip()
函數(shù)切換至讀模式后,position
切換為讀取位置,limit
切換為讀取限制:
這個變換也可以從 flip()
的源碼清晰的獲知:
public Buffer flip() { limit = position; position = 0; mark = -1; return this; }
當讀完之后,使用 clean()
函數(shù)清空緩存區(qū),可從源碼獲知,緩沖區(qū)又變成了初始化時的狀態(tài):
public Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
這里還有一種方法 compact()
,其作用是將未讀完的部分向前壓縮,然后切換至寫模式,不過需要注意的是,這是 ByteBuffer
中的方法:
接下來,將要結合代碼對上述內容進行深入理解;
這里用到了一個自定義的工具類 ByteBufferUtil
,由于篇幅原因,自行從我的 Github 上進行獲?。?a rel="external nofollow" target="_blank"> ByteBufferUtil.java;
編寫一個測試類,對 ByteBuffer
的常用方法進行測試:
public class TestByteBufferReadWrite { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(10); // 寫入一個字節(jié)的數(shù)據(jù) buffer.put((byte) 0x73); debugAll(buffer); // 寫入一組五個字節(jié)的數(shù)據(jù) buffer.put(new byte[]{0x69, 0x64, 0x69, 0x6f, 0x74}); debugAll(buffer); // 獲取數(shù)據(jù) buffer.flip(); ByteBufferUtil.debugAll(buffer); System.out.println((char) buffer.get()); System.out.println((char) buffer.get()); ByteBufferUtil.debugAll(buffer); // 使用 compact 切換寫模式 buffer.compact(); ByteBufferUtil.debugAll(buffer); // 再次寫入 buffer.put((byte) 102); buffer.put((byte) 103); ByteBufferUtil.debugAll(buffer); } }
運行結果:
// 向緩沖區(qū)寫入了一個字節(jié)的數(shù)據(jù),此時 postition 為 1; +--------+-------------------- all ------------------------+----------------+ position: [1], limit: [10] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 73 00 00 00 00 00 00 00 00 00 |s......... | +--------+-------------------------------------------------+----------------+ // 向緩沖區(qū)寫入了五個字節(jié)的數(shù)據(jù),此時 postition 為 6; +--------+-------------------- all ------------------------+----------------+ position: [6], limit: [10] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 73 69 64 69 6f 74 00 00 00 00 |sidiot.... | +--------+-------------------------------------------------+----------------+ // 調用 flip() 切換至讀模式,此時 position 為 0,表示從第 0 個數(shù)據(jù)開始讀取; // 同時要注意,此時的 limit 為 6,表示 position=6 時內容就讀完了; +--------+-------------------- all ------------------------+----------------+ position: [0], limit: [6] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 73 69 64 69 6f 74 00 00 00 00 |sidiot.... | +--------+-------------------------------------------------+----------------+ // 讀取兩個字節(jié)的數(shù)據(jù); s i // 此時 position 變?yōu)?2; +--------+-------------------- all ------------------------+----------------+ position: [2], limit: [6] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 73 69 64 69 6f 74 00 00 00 00 |sidiot.... | +--------+-------------------------------------------------+----------------+ // 調用 compact() 切換至寫模式,此時 position 及其后面的數(shù)據(jù)被壓縮到 ByteBuffer 的前面; // 此時 position 為 4,會覆蓋之前的數(shù)據(jù); +--------+-------------------- all ------------------------+----------------+ position: [4], limit: [10] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 64 69 6f 74 6f 74 00 00 00 00 |diotot.... | +--------+-------------------------------------------------+----------------+ // 再次寫入兩個字節(jié)的數(shù)據(jù),之前的 0x6f 0x74 被覆蓋; +--------+-------------------- all ------------------------+----------------+ position: [6], limit: [10] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 64 69 6f 74 66 67 00 00 00 00 |diotfg.... | +--------+-------------------------------------------------+----------------+ Process finished with exit code 0
空間分配
在上述內容中,我們使用 allocate()
方法來為 ByteBuffer 分配空間,當然還有其他方法也可以為 ByteBuffer 分配空間;
public class TestByteBufferAllocate { public static void main(String[] args) { System.out.println(ByteBuffer.allocate(16).getClass()); System.out.println(ByteBuffer.allocateDirect(16).getClass()); /* class java.nio.HeapByteBuffer - java 堆內存, 讀寫效率低, 受垃圾回收 GC 的影響; class java.nio.DirectByteBuffer - 直接內存,讀寫效率高(少一次拷貝),不會受 GC 的影響; - 使用完后 需要徹底的釋放,以免內存泄露; */ } }
寫入數(shù)據(jù)
- 調用
channel
的read()
方法:channel.read(buf)
; - 調用
buffer
的put()
方法:buffer.put((byte) 127)
;
讀取數(shù)據(jù)
rewind
public Buffer rewind() { position = 0; mark = -1; return this; }
rewind()
的作用是將 position
設置為0,這意味著下一次讀取或寫入操作將從緩沖區(qū)的開頭開始。
@Test public void testRewind() { // rewind 從頭開始讀 ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put(new byte[]{'s', 'i', 'd', 'i', 'o', 't'}); buffer.flip(); buffer.get(new byte[6]); debugAll(buffer); buffer.rewind(); System.out.println((char) buffer.get()); }
運行結果:
+--------+-------------------- all ------------------------+----------------+
position: [6], limit: [6]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 00 00 00 00 00 00 00 00 00 00 |sidiot..........|
+--------+-------------------------------------------------+----------------+// 從頭讀到第一個字符 's';
sProcess finished with exit code 0
mark
和 reset
public Buffer mark() { mark = position; return this; }
mark()
用于在緩沖區(qū)中設置標記;
public Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
reset()
用于返回到標記位置;
@Test public void testMarkAndReset() { // mark 做一個標記,用于記錄 position 的位置;reset 是將 position 重置到 mark 的位置; ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put(new byte[]{'s', 'i', 'd', 'i', 'o', 't'}); buffer.flip(); System.out.println((char) buffer.get()); System.out.println((char) buffer.get()); buffer.mark(); // 添加標記為索引2的位置; System.out.println((char) buffer.get()); System.out.println((char) buffer.get()); debugAll(buffer); buffer.reset(); // 將 position 重置到索引2; debugAll(buffer); System.out.println((char) buffer.get()); System.out.println((char) buffer.get()); }
運行結果:
s
i
d
i+--------+-------------------- all ------------------------+----------------+
position: [4], limit: [6]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 00 00 00 00 00 00 00 00 00 00 |sidiot..........|
+--------+-------------------------------------------------+----------------+// position 從4重置為2;
+--------+-------------------- all ------------------------+----------------+
position: [2], limit: [6]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 00 00 00 00 00 00 00 00 00 00 |sidiot..........|
+--------+-------------------------------------------------+----------------+d
iProcess finished with exit code 0
get(i)
get(i)
不會改變讀索引的位置;
@Test public void testGet_i() { // get(i) 不會改變讀索引的位置; ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put(new byte[]{'s', 'i', 'd', 'i', 'o', 't'}); buffer.flip(); System.out.println((char) buffer.get(2)); debugAll(buffer); }
運行結果:
d
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [6]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 00 00 00 00 00 00 00 00 00 00 |sidiot..........|
+--------+-------------------------------------------------+----------------+Process finished with exit code 0
字符串與 ByteBuffer 的相互轉換
getBytes
public byte[] getBytes() { return StringCoding.encode(coder(), value); }
字符串調用 getByte()
方法獲得 byte
數(shù)組,將 byte
數(shù)組放入 ByteBuffer 中:
@Test public void testGetBytes() { ByteBuffer buffer = ByteBuffer.allocate(16); buffer.put("sidiot".getBytes()); debugAll(buffer); }
運行結果:
+--------+-------------------- all ------------------------+----------------+
position: [6], limit: [16]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 00 00 00 00 00 00 00 00 00 00 |sidiot..........|
+--------+-------------------------------------------------+----------------+Process finished with exit code 0
charset
public final ByteBuffer encode(String str) { return encode(CharBuffer.wrap(str)); }
通過 StandardCharsets
的 encode()
方法獲得 ByteBuffer,此時獲得的 ByteBuffer 為讀模式,無需通過 flip()
切換模式:
@Test public void testCharset() { ByteBuffer buffer = StandardCharsets.UTF_8.encode("sidiot"); debugAll(buffer); System.out.println(StandardCharsets.UTF_8.decode(buffer)); }
運行結果:
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [6]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 |sidiot |
+--------+-------------------------------------------------+----------------+sidiot
Process finished with exit code 0
wrap
public static ByteBuffer wrap(byte[] array, int offset, int length) { try { return new HeapByteBuffer(array, offset, length, null); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } }
將字節(jié)數(shù)組傳給 wrap()
方法,通過該方法獲得 ByteBuffer,此時的 ByteBuffer 同樣為讀模式:
@Test public void testWrap() { ByteBuffer buffer = ByteBuffer.wrap("sidiot".getBytes()); debugAll(buffer); }
運行結果:
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [6]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 69 64 69 6f 74 |sidiot |
+--------+-------------------------------------------------+----------------+
Process finished with exit code 0
后記
參考:
以上就是Netty NIO之ByteBuffer類基礎學習的詳細內容,更多關于Netty框架ByteBuffer類的資料請關注腳本之家其它相關文章!
相關文章
spring boot metrics監(jiān)控指標使用教程
這篇文章主要為大家介紹了針對應用監(jiān)控指標暴露spring boot metrics監(jiān)控指標的使用教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-02-02java中extends與implements的區(qū)別淺談
java中extends與implements的區(qū)別淺談,需要的朋友可以參考一下2013-03-03