成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

讓我們一起玩轉 ByteBuffer

開發 前端
Buffer 存在的目的是為了減少與設備(例如磁盤)的交互頻率,在之前的博客中也提到過「磁盤的讀寫是很昂貴的操作」。

[[442736]]

本文轉載自微信公眾號「SH的全棧筆記」,作者SH的全棧筆記。轉載本文請聯系SH的全棧筆記公眾號。

為什么要講 Buffer

首先為什么一個小小的 Buffer 我們需要單獨拎出來聊?或者說,Buffer 具體是在哪些地方被用到的呢?

例如,我們從磁盤上讀取一個文件,并不是直接就從磁盤加載到內存中,而是首先會將磁盤中的數據復制到內核緩沖區中,然后再將數據從內核緩沖區復制到用戶緩沖區內,在圖里看起來就是這樣:

從磁盤讀取文件

再比如,我們往磁盤上寫文件,也不是直接將數據寫到磁盤。而是將數據從用戶緩沖區寫到內核緩沖區,由操作系統擇機將其刷入磁盤,圖跟上面這個差不多,就不畫了,自行理解。

再再比如,服務器接受客戶端發過來的數據時,也不是直接到用戶態的 Buffer 中。而是會先從網卡到內核態的 Buffer 中,再從內核態的 Buffer 中復制到用戶態的 Buffer 中。

那為什么要這么麻煩呢?復制來復制去的,首先我們用排除法排除這樣做是為了好玩。

Buffer 存在的目的是為了減少與設備(例如磁盤)的交互頻率,在之前的博客中也提到過「磁盤的讀寫是很昂貴的操作」。那昂貴在哪里呢?簡單來說,和設備的交互(例如和磁盤的IO)會設計到操作系統的中斷。中斷需要保存之前的進程運行的上下文,中斷結束之后又需要恢復這個上下文,并且還涉及到內核態和用戶態的切換,總體上是個耗時的操作。

看到這里,不熟悉操作系統的話可能會有點疑惑。例如:

  • 啥是用戶態
  • 啥是內核態

大家可以去看看我之前寫的文章 《簡單聊聊用戶態和內核態的區別》

Buffer 的使用

我們通過 Java 中 NIO 包中實現的 Buffer 來給大家講解,Buffer 總共有 7 種實現,就包含了 Java 中實現的所有數據類型。

Buffer的種類 (1)

本篇文章中,我們使用的是 ByteBuffer,其常用的方法都有:

  • put
  • get
  • flip
  • rewind
  • mark
  • reset
  • clear

接下來我們就通過實際的例子來了解這些方法。

put

put 就是往 ByteBuffer 里寫入數據,其有有很多重載的實現:

  1. public ByteBuffer put(ByteBuffer src) {...} 
  2.  
  3. public ByteBuffer put(byte[] src, int offset, int length) {...} 
  4.  
  5. public final ByteBuffer put(byte[] src) {...} 

我們可以直接傳入 ByteBuffer 對象,也可以直接傳入原生的 byte 數組,還可以指定寫入的 offset 和長度等等。接下來看個具體的例子:

  1. public static void main(String[] args) { 
  2.     ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.     buffer.put(new byte[]{'s','h'}); 

為了能讓大家更直觀的看出 ByteBuffer 內部的情況,我將它整理成了圖的形式。當上面的代碼運行完之后 buffer 的內部長這樣:

put

當你嘗試使用 System.out.println(buffer) 去打印變量 buffer 的時候,你會看到這樣的結果:

  1. java.nio.HeapByteBuffer[pos=2 lim=16 cap=16] 

圖里、控制臺里都有 position 和 limit 變量,capacity 大家能理解,就是我們創建這個 ByteBuffer 的制定的大小 16。

而至于另外兩個變量,相信大家從圖中也可以看出來,position 變量指向的是下一次要寫入的下標,上面的代碼我們只寫入了 2 個字節,所以 position 指向的是 2,而這個 limit 就比較有意思了,這個在后面的使用中結合例子一起講。

get

get 是從 ByteBuffer 中獲取數據。

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'s','h'}); 
  4.   System.out.println(buffer.get()); 

如果你運行完上面的代碼你會發現,打印出來的結果是 0 ,并不是我們期望的 s 的 ASCII 碼 115。

首先告訴大家結論,這是符合預期的,這個時候就不應該能獲取到值。我們來看看 get 的源碼:

  1. public byte get() { return hb[ix(nextGetIndex())]; } 
  2.  
  3. protected int ix(int i) { return i + offset; } 
  4.  
  5. final int nextGetIndex() {                           
  6.   int p = position; 
  7.   if (p >= limit) 
  8.     throw new BufferUnderflowException(); 
  9.   // 這里 position 會往后移動一位 
  10.   position = p + 1; 
  11.   return p; 

當前 position 是 2,而 limit 是 16,所以最終 nextGetIndex 計算出來的值就是變量 p 的值 2 ,再過一次 ix ,那就是 2 + 0 = 2,這里的 offset 的值默認為 0 。

所以簡單來說,最終會取到下標為 2 的數據,也就是下圖這樣。

所以我們當然獲取不到數據。但是這里需要關注的是,調用 get 方法雖然沒有獲取到任何數據,但是會使得 position 指針往后移動。換句話說,會占用一個位置。如果連續調用幾次這種 get 之后,再調用 put 方法寫入數據,就會造成有幾個位置沒有賦值。舉個例子,假設我們運行以下代碼:

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'s','h'}); 
  4.  
  5.   buffer.get(); 
  6.   buffer.get(); 
  7.   buffer.get(); 
  8.   buffer.get(); 
  9.  
  10.   buffer.put(new byte[]{'e'}); 

數據就會變成下圖這樣,position 會往后移動

那你可能會問,那我真的需要獲取數據咋辦?在這種情況下,可以像這樣獲取:

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'s'}); 
  4.   System.out.println(buffer.get(0)); // 115 

傳入我們想要獲取的下標,就可以直接獲取到,并且不會造成 position 的后移。

看到這那你更懵逼了,合著 get() 就沒法用唄?還必須要給個 index。這就需要聊一下另一個方法 flip了。

flip

廢話不多說,先看看例子:

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'s''h'}); // java.nio.HeapByteBuffer[pos=2 lim=16 cap=16] 
  4.   buffer.flip(); 
  5.   System.out.println(buffer); // java.nio.HeapByteBuffer[pos=0 lim=2 cap=16] 

有意思的事情發生了,調用了 flip 之后,position 從 2 變成了 0,limit 從 16 變成了 2。

這個單詞是「翻動」的意思,我個人的理解是像翻東西一樣把之前存的東西全部翻一遍

你會發現,position 變成了 0,而 limit 變成 2,這個范圍剛好是有值的區間。

接下來就更有意思了:

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'s''h'}); 
  4.   buffer.flip(); 
  5.   System.out.println((char)buffer.get()); // s 
  6.   System.out.println((char)buffer.get()); // h 

調用了 flip 之后,之前沒法用的 get() 居然能用了。結合 get 中給的源碼不難分析出來,由于 position 變成了 0,最終計算出來的結果就是 0,同時使 position 向后移動一位。

終于到這了,你可以理解成 Buffer 有兩種狀態,分別是:

  • 讀模式
  • 寫模式

剛剛創建出來的 ByteBuffer 就處于一個寫模式的狀態,通過調用 flip 我們可以將 ByteBuffer 切換成讀模式。但需要注意,這里講的讀、寫模式只是一個邏輯上的概念。

舉個例子,當調用 flip 切換到所謂的寫模式之后,依然能夠調用 put 方法向 ByteBuffer 中寫入數據。

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'s''h'}); 
  4.   buffer.flip(); 
  5.   buffer.put(new byte[]{'e'}); 

這里的 put 操作依然能成功,但你會發現最后寫入的 e 覆蓋了之前的數據,現在 ByteBuffer 的值變成了 eh 而不是 sh 了。

flip_put

所以你現在應該能夠明白,讀模式、寫模式更多的含義應該是:

  • 方便你讀的模式
  • 方便你寫的模式

順帶一提,調用 flip 進入寫讀模式之后,后續如果調用 get() 導致 position 大于等于了 limit 的值,程序會拋出 BufferUnderflowException 異常。這點從之前 get 的源碼也可以看出來。

rewind

rewind 你也可以理解成是運行在讀模式下的命令,給大家看個例子:

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'s''h'}); 
  4.   buffer.flip(); 
  5.   System.out.println((char)buffer.get()); // s 
  6.   System.out.println((char)buffer.get()); // h 
  7.  
  8.   // 從頭開始讀 
  9.   buffer.rewind(); 
  10.  
  11.   System.out.println((char)buffer.get()); // s 
  12.   System.out.println((char)buffer.get()); // h 

所謂的從頭開始讀就是把 position 給歸位到下標為 0 的位置,其源碼也很簡單:

  1. public final Buffer rewind() { 
  2.   position = 0; 
  3.   mark = -1; 
  4.   return this; 

rewind

就是簡單的把 position 賦值為 0,把 mark 賦值為 -1。那這個 mark 又是啥東西?這就是我們下一個要聊的方法。

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'a''b''c''d'}); 
  4.    
  5.   // 切換到讀模式 
  6.   buffer.flip(); 
  7.   System.out.println((char) buffer.get()); // a 
  8.   System.out.println((char) buffer.get()); // b 
  9.  
  10.   // 控記住當前的 position 
  11.   buffer.mark(); 
  12.    
  13.   System.out.println((char) buffer.get()); // c 
  14.   System.out.println((char) buffer.get()); // d 
  15.  
  16.   // 將 position reset 到 mark 的位置 
  17.   buffer.reset(); 
  18.   System.out.println((char) buffer.get()); // c 
  19.   System.out.println((char) buffer.get()); // d 

可以看到的是 ,我們在 position 等于 2 的時候,調用了 mark 記住了 position 的位置。然后遍歷完了所有的數據。然后調用 reset 使得 position 回到了 2 的位置,我們繼續調用 get ,c d 就又可以被打印出來了。

clear

clear 表面意思看起來是將 buffer 清空的意思,但其實不是,看這個:

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'a''b''c''d'}); 

put 完之后,buffer 的情況是這樣的。

當我們調用完 clear 之后,buffer 就會變成這樣。

所以,你可以理解為,調用 clear 之后只是切換到了寫模式,因為這個時候往里面寫數據,會覆蓋之前寫的數據,相當于起到了 clear 作用,再舉個例子:

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put(new byte[]{'a''b''c''d'}); 
  4.   buffer.clear(); 
  5.   buffer.put(new byte[]{'s','h'}); 

可以看到,運行完之后 buffer 的數據變成了 shcd,后寫入的數據將之前的數據給覆蓋掉了。

除了 clear 可以切換到寫模式之外,還有另一個方法可以切換,這就是本篇要講的最后一個方法 compact。

compact

先一句話給出 compact 的作用:將還沒有讀完的數據挪到 Buffer 的首部,并切換到寫模式,代碼如下:

  1. public static void main(String[] args) { 
  2.   ByteBuffer buffer = ByteBuffer.allocate(16); 
  3.   buffer.put("abcd".getBytes(StandardCharsets.UTF_8)); 
  4.  
  5.   // 切換到讀模式 
  6.   buffer.flip(); 
  7.   System.out.println((char) buffer.get()); // a 
  8.  
  9.   // 將沒讀過的數據, 移到 buffer 的首部 
  10.   buffer.compact(); // 此時 buffer 的數據就會變成 bcdd 

當運行完 flip 之后,buffer 的狀態應該沒什么問題了:

運行完 flip 之后

而 compact 之后發生了什么呢?簡單來說就兩件事:

  • 將 position 移動至對應的位置
  • 將沒有讀過的數據移動到 buffer 的首部

這個對應是啥呢?先給大家舉例子;例如沒有讀的數據是 bcd,那么 position 就為 3;如果沒有讀的數據為 cd,position 就為 2。所以你發現了,position 的值為沒有讀過的數據的長度。

從 buffer 內部實現機制來看,凡是在 position - limit 這個區間內的,都算沒有讀過的數據

所以,當運行完 compact 之后,buffer 長這樣:

運行完 compact 之后

limit 為 16 是因為 compact 使 buffer 進入了所謂的寫模式。

EOF

還有一些其他的方法就不在這里列舉了,大家感興趣可以自己去玩玩,都沒什么理解上的難度了。之后可能會再專門寫一寫 Channel 和 Selector,畢竟 Java 的 nio 三劍客,感興趣的可以關注一下。

 

責任編輯:武曉燕 來源: SH的全棧筆記
相關推薦

2022-03-31 18:59:43

數據庫InnoDBMySQL

2021-08-27 07:06:10

IOJava抽象

2022-03-08 17:52:58

TCP格式IP

2021-11-26 07:00:05

反轉整數數字

2021-07-15 07:23:28

Singlefligh設計

2022-02-14 10:16:22

Axios接口HTTP

2022-02-14 07:03:31

網站安全MFA

2016-09-06 10:39:30

Dell Techno

2022-06-26 09:40:55

Django框架服務

2023-08-14 08:38:26

反射reflect結構體

2022-01-17 06:59:40

Grep指令linux

2022-07-10 23:15:46

Go語言內存

2023-08-02 08:35:54

文件操作數據源

2022-08-01 07:57:03

數組操作內存

2012-04-14 20:47:45

Android

2021-07-31 11:40:55

Openresty開源

2021-12-16 12:01:21

區塊鏈Libra貨幣

2022-09-26 14:25:55

Flowable流程ID

2014-02-25 08:59:14

2021-11-09 23:54:19

開發SMI Linkerd
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久一区二区三区 | 久久久久电影 | 99精品欧美一区二区蜜桃免费 | 国产亚洲成av人片在线观看桃 | 亚洲看片网站 | 日本精品一区二区三区在线观看视频 | 毛片大全 | 日本三级播放 | 日韩一区二区福利视频 | 婷婷色国产偷v国产偷v小说 | 久久久www成人免费精品 | 成人在线黄色 | 亚洲第一在线 | 中文字幕在线观看av | 亚洲综合色视频在线观看 | 亚洲狠狠爱 | 精品自拍视频 | 国产成人精品一区二 | 国产一区欧美 | 精品视频在线观看 | 精品国产乱码一区二区三区a | 欧美精品久久久 | 日本久久久一区二区三区 | 亚洲韩国精品 | 91婷婷韩国欧美一区二区 | 日韩欧美在线播放 | 91久久久久久 | 成人午夜黄色 | 久久不卡日韩美女 | 在线免费观看成年人视频 | 国产精品亚洲一区 | 欲色av| 91中文字幕在线观看 | 成人午夜在线 | 久久午夜精品福利一区二区 | 色婷婷国产精品综合在线观看 | 日韩久久久久 | 亚洲a视频 | 国产欧美日韩在线观看 | 国产一区欧美 | 国内成人免费视频 |