Netty之JavaNIO編程模型介紹01
??我們在前面的BIO的基礎上我們來繼續介紹下NIO的內容
一、Java NIO 基本介紹
??Java NIO 全稱 java non-blocking IO,是指 JDK 提供的新 API。從 JDK1.4 開始,Java 提供了一系列改進的輸入/輸出的新特性,被統稱為 NIO(即 New IO),是同步非阻塞的
??NIO 相關類都被放在 java.nio 包及子包下,并且對原 java.io 包中的很多類進行改寫。
??NIO是面向緩沖區
,或者面向 塊
編程的。數據讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動,這就增加了處理過程中的靈活性,使用它可以提供非阻塞式的高伸縮性網絡
??Java NIO的非阻塞模式
,使一個線程從某通道發送請求或者讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取,而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此,一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。
??通俗理解:NIO是可以做到用一個線程來處理多個操作的。假設有10000個請求過來,根據實際情況,可以分配50或者100個線程來處理。不像之前的阻塞IO那樣,非得分配10000個。
??HTTP2.0使用了多路復用
的技術,做到同一個連接并發處理多個請求,而且并發請求的數量比HTTP1.1大了好幾個數量級。
二、NIO和BIO的比較
-
BIO 以
流
的方式處理數據,而 NIO 以塊
的方式處理數據,塊 I/O 的效率比流 I/O 高很多 -
BIO 是
阻塞
的,NIO 則是非阻塞
的 -
BIO基于字節流和字符流進行操作,而 NIO 基于
Channel(通道)
和Buffer(緩沖區)
進行操作,數據總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。Selector(選擇器)用于監聽多個通道的事件(比如:連接請求,數據到達等),因此使用單個線程就可以監聽多個客戶端通道
三、NIO三大核心
??下圖描述了 Buffer,Channel,Selector三者的關系
- 每個channel 都會對應一個Buffer
- Selector 對應一個線程, 一個線程對應多個channel(連接)
- 該圖反應了有三個channel 注冊到 該selector //程序
- 程序切換到哪個channel 是有事件決定的, Event 就是一個重要的概念
- Selector 會根據不同的事件,在各個通道上切換
- Buffer 就是一個內存塊 , 底層是有一個數組
- 數據的讀取寫入是通過Buffer, 這個和BIO , BIO 中要么是輸入流,或者是輸出流, 不能雙向,但是NIO的Buffer 是可以讀也可以寫, 需要 flip 方法切換channel 是雙向的, 可以返回底層操作系統的情況, 比如Linux , 底層的操作系統
- 通道就是雙向的.
3.1緩沖區Buffer
基本介紹
??緩沖區(Buffer):緩沖區本質上是一個可以讀寫數據的內存塊,可以理解成是一個容器對象(含數組),該對象提供了一組方法,可以更輕松地使用內存塊,,緩沖區對象內置了一些機制,能夠跟蹤和記錄緩沖區的狀態變化情況。Channel 提供從文件、網絡讀取數據的渠道,但是讀取或寫入的數據都必須經由 Buffer,
Buffer 類及其子類
- 在 NIO 中,Buffer 是一個頂層父類,它是一個抽象類, 類的層級關系圖:
通過名稱也比較直觀的能看出每個具體Buffer的作用。
- Buffer類定義了所有的緩沖區都具有的四個屬性來提供關于其所包含的數據元素的信息:
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
屬性 | 描述 |
---|---|
Capacity | 容量,即可以容納的最大數據量;在緩沖區創建時被設定并且不能改變 |
Limit | 表示緩沖區的當前終點,不能對緩沖區超過極限的位置進行讀寫操作。且極限是可以修改的 |
Position | 位置,下一個要被讀或寫的元素的索引,每次讀寫緩沖區數據時都會改變改值,為下次讀寫作準備 |
Mark | 標記 |
- Buffer類相關方法一覽
public abstract class Buffer {
//JDK1.4時,引入的api
public final int capacity( )//返回此緩沖區的容量
public final int position( )//返回此緩沖區的位置
public final Buffer position (int newPositio)//設置此緩沖區的位置
public final int limit( )//返回此緩沖區的限制
public final Buffer limit (int newLimit)//設置此緩沖區的限制
public final Buffer mark( )//在此緩沖區的位置設置標記
public final Buffer reset( )//將此緩沖區的位置重置為以前標記的位置
public final Buffer clear( )//清除此緩沖區, 即將各個標記恢復到初始狀態,但是數據并沒有真正擦除, 后面操作會覆蓋
public final Buffer flip( )//反轉此緩沖區
public final Buffer rewind( )//重繞此緩沖區
public final int remaining( )//返回當前位置與限制之間的元素數
public final boolean hasRemaining( )//告知在當前位置和限制之間是否有元素
public abstract boolean isReadOnly( );//告知此緩沖區是否為只讀緩沖區
//JDK1.6時引入的api
public abstract boolean hasArray();//告知此緩沖區是否具有可訪問的底層實現數組
public abstract Object array();//返回此緩沖區的底層實現數組
public abstract int arrayOffset();//返回此緩沖區的底層實現數組中第一個緩沖區元素的偏移量
public abstract boolean isDirect();//告知此緩沖區是否為直接緩沖區
}
- ByteBuffer
??從前面可以看出對于 Java 中的基本數據類型(boolean除外),都有一個 Buffer 類型與之相對應,最常用的自然是ByteBuffer 類(二進制數據),該類的主要方法如下
public abstract class ByteBuffer {
//緩沖區創建相關api
public static ByteBuffer allocateDirect(int capacity)//創建直接緩沖區
public static ByteBuffer allocate(int capacity)//設置緩沖區的初始容量
public static ByteBuffer wrap(byte[] array)//把一個數組放到緩沖區中使用
//構造初始化位置offset和上界length的緩沖區
public static ByteBuffer wrap(byte[] array,int offset, int length)
//緩存區存取相關API
public abstract byte get( );//從當前位置position上get,get之后,position會自動+1
public abstract byte get (int index);//從絕對位置get
public abstract ByteBuffer put (byte b);//從當前位置上添加,put之后,position會自動+1
public abstract ByteBuffer put (int index, byte b);//從絕對位置上put
}
3.2 通道Channel
基本介紹
- NIO的通道類似于流,但有些區別如下:
- 通道可以同時進行讀寫,而流只能讀或者只能寫
- 通道可以實現異步讀寫數據
- 通道可以從緩沖讀數據,也可以寫數據到緩沖
2) BIO 中的 stream 是單向的,例如 FileInputStream 對象只能進行讀取數據的操作,而 NIO 中的通道(Channel)是雙向的,可以讀操作,也可以寫操作。
3) Channel在NIO中是一個接口public interface Channel extends Closeable{}
4) 常用的 Channel 類有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。【ServerSocketChanne 類似 ServerSocket , SocketChannel 類似 Socket】
- FileChannel 用于文件的數據讀寫,DatagramChannel 用于 UDP 的數據讀寫,ServerSocketChannel 和 SocketChannel 用于 TCP 的數據讀寫。
FileChannel 類
FileChannel主要用來對本地文件進行 IO 操作,常見的方法有
public int read(ByteBuffer dst) ,從通道讀取數據并放到緩沖區中
public int write(ByteBuffer src) ,把緩沖區的數據寫到通道中
public long transferFrom(ReadableByteChannel src, long position, long count),從目標通道中復制數據到當前通道
public long transferTo(long position, long count, WritableByteChannel target),把數據從當前通道復制給目標通道
實例1-本地文件寫數據
??通過前面介紹的內容完成一些簡單的NIO文件操作,代碼如下:
package com.dpb.netty.nio;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-28 11:37
*/
public class NioChannel01 {
public static void main(String[] args) throws Exception{
String str = "hello,bobokaoya";
//創建一個輸出流->channel
FileOutputStream fileOutputStream = new FileOutputStream("c:\\tools\\netty.txt");
//通過 fileOutputStream 獲取 對應的 FileChannel
//這個 fileChannel 真實 類型是 FileChannelImpl
FileChannel fileChannel = fileOutputStream.getChannel();
//創建一個緩沖區 ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//將 str 放入 byteBuffer
byteBuffer.put(str.getBytes());
//對byteBuffer 進行flip
byteBuffer.flip();
//將byteBuffer 數據寫入到 fileChannel
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
}
實例2-本地文件讀數據
package com.dpb.netty.nio;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-28 11:41
*/
public class NioChannel02 {
public static void main(String[] args) throws Exception {
//創建文件的輸入流
File file = new File("c:\\tools\\netty.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//通過fileInputStream 獲取對應的FileChannel -> 實際類型 FileChannelImpl
FileChannel fileChannel = fileInputStream.getChannel();
//創建緩沖區
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//將 通道的數據讀入到Buffer
fileChannel.read(byteBuffer);
//將byteBuffer 的 字節數據 轉成String
System.out.println(new String(byteBuffer.array()));
fileInputStream.close();
}
}
實例3-使用一個Buffer完成文件讀取
package com.dpb.netty.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-28 11:44
*/
public class NioChannel03 {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel fileChannel01 = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel fileChannel02 = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true) { //循環讀取
//這里有一個重要的操作,一定不要忘了
/*
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
*/
byteBuffer.clear(); //清空buffer
int read = fileChannel01.read(byteBuffer);
System.out.println("read =" + read);
if(read == -1) { //表示讀完
break;
}
//將buffer 中的數據寫入到 fileChannel02 -- 2.txt
byteBuffer.flip();
fileChannel02.write(byteBuffer);
}
//關閉相關的流
fileInputStream.close();
fileOutputStream.close();
}
}
實例4-拷貝文件transferFrom 方法
??接下來我們同transferFrom方法來實現一個文件的復制操作。
package com.dpb.netty.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
/**
* @program: netty4demo
* @description:
* @author: 波波烤鴨
* @create: 2019-12-28 11:49
*/
public class NioChannel04 {
public static void main(String[] args) throws Exception {
//創建相關流
FileInputStream fileInputStream = new FileInputStream("c:\\tools\\a1.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("c:\\tools\\a2.jpg");
//獲取各個流對應的filechannel
FileChannel sourceCh = fileInputStream.getChannel();
FileChannel destCh = fileOutputStream.getChannel();
//使用transferForm完成拷貝
destCh.transferFrom(sourceCh,0,sourceCh.size());
//關閉相關通道和流
sourceCh.close();
destCh.close();
fileInputStream.close();
fileOutputStream.close();
}
}
關于Buffer 和 Channel的注意事項和細節
-
ByteBuffer 支持類型化的put 和 get, put 放入的是什么數據類型,get就應該使用相應的數據類型來取出,否則可能有 BufferUnderflowException 異常。
-
可以將一個普通Buffer 轉成只讀Buffer [舉例說明]
NIO 還提供了 MappedByteBuffer, 可以讓文件直接在內存(堆外的內存)中進行修改, 而如何同步到文件由NIO 來完成. -
前面我們講的讀寫操作,都是通過一個Buffer 完成的,NIO 還支持 通過多個Buffer (即 Buffer 數組) 完成讀寫操作,即 Scattering 和 Gathering
3.3 選擇器Selector
基本介紹
??Java 的 NIO,用非阻塞的 IO 方式。可以用一個線程,處理多個的客戶端連接,就會使用到Selector(選擇器)
??Selector 能夠檢測多個注冊的通道上是否有事件發生(注意:多個Channel以事件的方式可以注冊到同一個Selector),如果有事件發生,便獲取事件然后針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接和請求。
??只有在 連接/通道 真正有讀寫事件發生時,才會進行讀寫,就大大地減少了系統開銷,并且不必為每個連接都創建一個線程,不用去維護多個線程
避免了多線程之間的上下文切換導致的開銷
特點再說明:
- Netty 的 IO 線程 NioEventLoop 聚合了 Selector(選擇器,也叫多路復用器),可以同時并發處理成百上千個客戶端連接。
- 當線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程可以進行其他任務。
- 線程通常將非阻塞 IO 的空閑時間用于在其他通道上執行 IO 操作,所以單獨的線程可以管理多個輸入和輸出通道。
- 由于讀寫操作都是非阻塞的,這就可以充分提升 IO 線程的運行效率,避免由于頻繁 I/O 阻塞導致的線程掛起。
- 一個 I/O 線程可以并發處理 N 個客戶端連接和讀寫操作,這從根本上解決了傳統同步阻塞 I/O 一連接一線程模型,架構的性能、彈性伸縮能力和可靠性都得到了極大的提升。
Selector類相關方法
??Selector 類是一個抽象類, 常用方法和說明如下:
public abstract class Selector implements Closeable {
public static Selector open();//得到一個選擇器對象
//監控所有注冊的通道,當其中有 IO 操作可以進行時,將對應的 SelectionKey 加入到內部集合中并返回,參數用來設置超時時間
public int select(long timeout);
public Set<SelectionKey> selectedKeys();//從內部集合中得到所有的 SelectionKey
}
注意事項
??NIO中的 ServerSocketChannel功能類似ServerSocket,SocketChannel功能類似Socket
selector 相關方法說明
selector.select()//阻塞
selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回
selector.wakeup();//喚醒selector
selector.selectNow();//不阻塞,立馬返還
智能推薦
Netty入門之Netty模型(二)
Netty入門之Netty模型(二) 1. 前言 上一節我們了解了基礎的IO線程模型,而Netty是對主從Reactor多線程模型的改進。 不了解的可以看看這篇博客:Netty入門之IO線程模型(一) 這一節,我們就來了解下Netty模型,并通過一段代碼實現來了解Netty的基本使用。 2. Netty模型 – 工作原理圖 說明: Netty抽象出兩組線程池BossGroup和Work...
Netty之Reactor線程模型
Reactor 線程模型 一、傳統線程模型與NIO模型 1. BIO 模型 傳統 I/O 模型為同步阻塞式 I/O 模型,對于網絡I/O而言,需要賦予每個連接一個線程,同時該線程會阻塞于I/O事件,無法操作執行其他任務 1.1 處理模型 每當有新連接到來時,創建一個新的線程以對新連接進行處理,可用***線程池***進行優化,但仍然無法應對高并發服務 1.2 服務端代碼 2. NIO 模型 NIO ...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
猜你喜歡
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...