天天看點

RPC架構設計----NIO程式設計緩沖區Buffer

1 NIO介紹

Java NIO 全稱java non-blocking IO ,是指 JDK 提供的新 API。從 JDK1.4 開始,Java 提供了一系列改進的輸入/輸出的新特性,被統稱為 NIO(即 New IO),是同步非阻塞的.

1. NIO 有三大核心部分:Channel(通道),Buffer(緩沖區), Selector(選擇器)

2. NIO是 面向緩沖區程式設計的。資料讀取到一個緩沖區中,需要時可在緩沖區中前後移動,這就增加了處理過程中的靈活性,使用它可以提供非阻塞式的高伸縮性網絡

3. Java NIO 的非阻塞模式,使一個線程從某通道發送請求或者讀取資料,但是它僅能得到目前可用的資料,如果目前沒有資料可用時,就什麼都不會擷取,而不是保持線程阻塞,是以直至資料變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此,一個線程請求寫入一些資料到某通道,但不需要等待它完全寫入, 這個線程同時可以去做别的事情。通俗了解:NIO 是可以做到用一個線程來處理多個操作的。假設有 10000 個請求過來,根據實際情況,可以配置設定50 或者 100 個線程來處理。不像之前的阻塞 IO 那樣,非得配置設定 10000 個

2 NIO和 BIO的比較

1. BIO 以流的方式處理資料,而 NIO 以緩沖區的方式處理資料,緩沖區 I/O 的效率比流 I/O 高很多

2. BIO 是阻塞的,NIO則是非阻塞的

3. BIO 基于位元組流和字元流進行操作,而 NIO 基于 Channel(通道)和 Buffer(緩沖區)進行操作,資料總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。Selector(選擇器)用于監聽多個通道的事件(比如:連接配接請求, 資料到達等),是以使用單個線程就可以監聽多個用戶端通道

3 NIO 三大核心原理示意圖

一張圖描述 NIO 的 Selector 、 Channel 和 Buffer 的關系

1. 每個 channel 都會對應一個 Buffer

2. Selector 對應一個線程, 一個線程對應多個 channel(連接配接)

3. 每個 channel 都注冊到 Selector選擇器上

4. Selector不斷輪詢檢視Channel上的事件, 事件是通道Channel非常重要的概念

5. Selector 會根據不同的事件,完成不同的處理操作

6. Buffer 就是一個記憶體塊 , 底層是有一個數組

7. 資料的讀取寫入是通過 Buffer, 這個和 BIO , BIO 中要麼是輸入流,或者是輸出流, 不能雙向,但是NIO 的 Buffer 是可以讀也可以寫 , channel 是雙向的.

4 緩沖區(Buffer)

基本介紹

緩沖區(Buffer):緩沖區本質上是一個可以讀寫資料的記憶體塊,可以了解成是一個數組,該對象提供了一組方法,可以更輕松地使用記憶體塊,,緩沖區對象内置了一些機制,能夠跟蹤和記錄緩沖區的狀态變化情況。Channel 提供從網絡讀取資料的管道,但是讀取或寫入的資料都必須經由 Buffer.

Buffer常用API介紹

Buffer 類及其子類

在 NIO 中,Buffer是一個頂層父類,它是一個抽象類, 類的層級關系圖,常用的緩沖區分别對應byte,short, int, long,float,double,char 7種.

RPC架構設計----NIO程式設計緩沖區Buffer

緩沖區對象建立

RPC架構設計----NIO程式設計緩沖區Buffer

示例代碼:

package com.test.buffer;

import java.nio.ByteBuffer;

/**

* 建立緩沖區

*/

public class CreateBufferDemo {

  public static void main(String[] args) {

    //1.建立一個指定長度的緩沖區, 以ByteBuffer為例

    ByteBuffer byteBuffer = ByteBuffer.allocate(5);

    for (int i = 0; i < 5; i++) {

      System.out.println(byteBuffer.get());

}

    //在此調用會報錯--後續再讀緩沖區時着重講解

    //System.out.println(byteBuffer.get());

    //2.建立一個有内容的緩沖區

    ByteBuffer wrap = ByteBuffer.wrap("test".getBytes());

      System.out.println(wrap.get());

   }

 }

3. 緩沖區對象添加資料

RPC架構設計----NIO程式設計緩沖區Buffer

圖解:

RPC架構設計----NIO程式設計緩沖區Buffer

* 添加緩沖區

public class PutBufferDemo {

    ByteBuffer byteBuffer = ByteBuffer.allocate(10);

    System.out.println(byteBuffer.position());//0 擷取目前索引所在位置

    System.out.println(byteBuffer.limit());//10 最多能操作到哪個索引

    System.out.println(byteBuffer.capacity());//10 傳回緩沖區總長度

    System.out.println(byteBuffer.remaining());//10 還有多少個能操作

    //修改目前索引位置

    //byteBuffer.position(1);

    //修改最多能操作到哪個索引位置

    //byteBuffer.limit(9);

    //System.out.println(byteBuffer.position());//1 擷取目前索引所在位置

    //System.out.println(byteBuffer.limit());//9 最多能操作到哪個索引

    //System.out.println(byteBuffer.capacity());//10 傳回緩沖區總長度

//System.out.println(byteBuffer.remaining());//8 還有多少個能操作

    //添加一個位元組

    byteBuffer.put((byte) 97);

    System.out.println(byteBuffer.position());//1 擷取目前索引所在位置

    System.out.println(byteBuffer.remaining());//9 還有多少個能操作

    //添加一個位元組數組

    byteBuffer.put("abc".getBytes());

    System.out.println(byteBuffer.position());//4 擷取目前索引所在位置

    System.out.println(byteBuffer.remaining());//6 還有多少個能操作

    //當添加超過緩沖區的長度時會報錯

    byteBuffer.put("012345".getBytes());

    System.out.println(byteBuffer.position());//10 擷取目前索引所在位置

    System.out.println(byteBuffer.remaining());//0 還有多少個能操作

    System.out.println(byteBuffer.hasRemaining());// false 是否還能有操作的

數組

    // 如果緩存區存滿後, 可以調整position位置可以重複寫,這樣會覆寫之前存入索引的對

應的值

    byteBuffer.position(0);

4 緩沖區對象讀取資料  

RPC架構設計----NIO程式設計緩沖區Buffer

圖解:flip()方法

RPC架構設計----NIO程式設計緩沖區Buffer

圖解:clear()方法

RPC架構設計----NIO程式設計緩沖區Buffer

執行個體代碼:

* 從緩沖區中讀取資料

public class GetBufferDemo {

    //1.建立一個指定長度的緩沖區

    ByteBuffer allocate = ByteBuffer.allocate(10);

    allocate.put("0123".getBytes());

    System.out.println("position:" + allocate.position());//4

    System.out.println("limit:" + allocate.limit());//10

    System.out.println("capacity:" + allocate.capacity());//10

    System.out.println("remaining:" + allocate.remaining());//6

    //切換讀模式

    System.out.println("讀取資料--------------");

    allocate.flip();

System.out.println("capacity:" + allocate.capacity());//10

    for (int i = 0; i < allocate.limit(); i++) {

      System.out.println(allocate.get());

    //讀取完畢後.繼續讀取會報錯,超過limit值

    //System.out.println(allocate.get());

    //讀取指定索引位元組

    System.out.println("讀取指定索引位元組--------------");

    System.out.println(allocate.get(1));

    System.out.println("讀取多個位元組--------------");

    // 重複讀取

  allocate.rewind();

    byte[] bytes = new byte[4];

  allocate.get(bytes);

    System.out.println(new String(bytes));

    // 将緩沖區轉化位元組數組傳回

    System.out.println("将緩沖區轉化位元組數組傳回--------------");

    byte[] array = allocate.array();

    System.out.println(new String(array));

    // 切換寫模式,覆寫之前索引所在位置的值

    System.out.println("寫模式--------------");

    allocate.clear();

    allocate.put("abc".getBytes());

    System.out.println(new String(allocate.array()));

注意事項:

1. capacity:容量(長度)limit: 界限(最多能讀/寫到哪裡)posotion:位置(讀/寫哪個索引)

2. 擷取緩沖區裡面資料之前,需要調用flip方法

3. 再次寫資料之前,需要調用clear方法,但是資料還未消失,等再次寫入資料,被覆寫了才會消失。

下一篇: Spring面試題