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種.
緩沖區對象建立
示例代碼:
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. 緩沖區對象添加資料
圖解:
* 添加緩沖區
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 緩沖區對象讀取資料
圖解:flip()方法
圖解:clear()方法
執行個體代碼:
* 從緩沖區中讀取資料
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方法,但是資料還未消失,等再次寫入資料,被覆寫了才會消失。