柚子快報邀請碼778899分享:開發(fā)語言 Java NIO
1. IO分類概述
1.1 阻塞與非阻塞
????????阻塞(Blocking)和非阻塞(Nonblocking)是在計算機編程中用于描述I/O操作的兩個重要概念。阻塞與非阻塞描述的是線程在訪問某個資源時,在該資源沒有準備就緒期間的處理方式。
????????1、阻塞:阻塞是指在進行I/O操作時,當前線程會被掛起,等待數據的就緒或操作的完成。
在阻塞狀態(tài)下,線程會一直等待,直到條件滿足或超時才會繼續(xù)執(zhí)行阻塞式I/O操作會導致調用線程無法執(zhí)行其他任務,直到I/O操作完例如,在讀取文件時,如果沒有數據可用,線程會一直等待直到有數據可讀
????????2、非阻塞:非阻塞是指進行I/O操作時,當前線程不會被掛起,而是立即返回并繼續(xù)執(zhí)行其他任務。
在非阻塞狀態(tài)下,即使I/O操作無法立即完成,線程也可以繼續(xù)執(zhí)行其他操作,而不需要一直等待非阻塞式I/O操作通常會返回一個狀態(tài)或錯誤碼,指示操作是否完成或需要進一步處理應用程序可以通過不斷輪詢狀態(tài)來判斷是否可以進行下一步操作
1.2 同步與異步
????????同步(synchronous)與異步(asynchronous)描述的是線程在發(fā)出請求后,是否等待結果。
????????1、同步(Synchronous):同步是指程序按照順序執(zhí)行,并等待某個操作完成后再繼續(xù)執(zhí)行下一個操作。
在同步操作中,程序主動發(fā)起請求并等待結果返回,直到結果返回后才能繼續(xù)執(zhí)行后續(xù)操作同步操作通常是阻塞的,即在等待結果時當前線程會被掛起,無法執(zhí)行其他任務典型的同步操作包括函數調用、阻塞式I/O操作等
????????2、異步(Asynchronous):異步是指程序在發(fā)起某個操作后,不需要立即等待操作完成,而是繼續(xù)執(zhí)行后續(xù)的操作。
在異步操作中,程序不會阻塞等待結果的返回,而是通過回調、輪詢或事件通知等機制來獲取結果或處理完成的事件異步操作允許程序并發(fā)執(zhí)行多個任務,提高了系統的并發(fā)性和響應性。典型的異步操作包括異步函數調用、非阻塞式I/O操作、異步任務等
1.3 IO的分類
????????當涉及I/O操作時,可以根據是否阻塞和是否異步的角度將其分為以下四類:
????????1、阻塞式I/O(Blocking I/O)
特點:在進行I/O操作時,當前線程會被阻塞,直到數據就緒或操作完成工作原理:當進行I/O操作時,線程會等待直到數據準備好或操作完成,在等待期間,線程無法執(zhí)行其他任務
????????2、非阻塞式I/O(Non-Blocking I/O)
特點:進行I/O操作時,當前線程不會被阻塞,立即返回并繼續(xù)執(zhí)行其他任務工作原理:當進行I/O操作時,如果數據尚未準備好或操作無法立即完成,操作會立即返回一個狀態(tài)指示暫時不可用
????????3、I/O多路復用(I/O Multiplexing)
特點:允許一個線程同時監(jiān)視多個I/O通道的就緒狀態(tài),提高了系統的并發(fā)性能工作原理:通過操作系統提供的I/O復用機制,將多個I/O通道注冊到一個選擇器上,然后使用選擇器進行輪詢以確定哪些通道就緒
????????4、異步I/O(Asynchronous I/O)
特點:在進行I/O操作時,不需要立即等待操作完成,可以繼續(xù)執(zhí)行其他任務,通過回調或事件機制來處理操作結果工作原理:應用程序提交I/O請求后立即返回,并使用回調、輪詢或事件通知等方式來處理操作完成的通知
????????這四種I/O模型各有優(yōu)劣,并適用于不同的應用場景:
阻塞式I/O適用于簡單的同步操作非阻塞式I/O適用于需要處理多個連接的場景I/O多路復用適用于需要監(jiān)視多個連接的場景異步I/O適用于需要高性能和擴展性的場景
????????根據具體的應用需求和系統特點,可以選擇合適的I/O模型來實現高效的數據傳輸和處理。
2.?Java NIO
2.1 Java NIO概述
????????在Java編程中,經常聽到BIO,NIO,AIO等名詞,這些名詞一般指的是Java語言為實現不同類型的I/O操作所提供的API。
????????1、Java IO:Java IO是JDK 1.0版本其自帶的用于讀取和寫入數據的API。因其同步、阻塞的特征,被歸類為同步阻塞式IO(Blocking IO),即Java BIO。
????????2、Java NIO(New IO):是JDK 1.4開始提供的同步非阻塞式的IO操作API。因其同步、非阻塞的特征,開發(fā)者一般將Java NIO理解為Java Non-Blocking IO。
????????3、Java NIO 2.0:是JDK 1.7開始提供的異步非阻塞式的IO操作API,因其是在NIO的基礎上進行了改進,稱為NIO 2.0。因其異步、非阻塞的特征,開發(fā)者一般將Java NIO 2.0稱為Java AIO。
????????下面以一個Web服務器的例子介紹Java BIO和Java NIO在實際使用中的差別。
????????基于BIO的場景:
????????基于NIO的場景:
2.2 Java BIO模型
????????Java BIO和NIO采用了2種不同的模型。BIO使用了面向流(Stream Oriented)的模型。流(Stream)可以理解為從源節(jié)點到目標節(jié)點的數據通道,傳輸的數據像水流一樣從源節(jié)點流向目標節(jié)點。
????????面向流的特點:
單向的:一個流中的數據僅能從一個方向流向另一個方向面向字節(jié)的:程序每次可以從流中讀取1到多個字節(jié),或寫入1到多個字節(jié)無緩沖的:從流中讀取數據后,流中的數據消失順序訪問:僅能按順序逐個訪問流中的數據,不能在流中的數據中前后移動
2.3 Java NIO模型
????????Java NIO使用了面向緩沖區(qū)(Buffer Oriented)的,基于通道(Channel Based)的模型。模型的不同使得BIO和NIO在功能上和操作上有著不同的特點。
????????在面向緩沖的模型中,數據通過數據通道(Channel)被讀入/寫入到一個緩沖區(qū)(Buffer)中,然后從中進行處理。
????????可以使用一個生活中的例子來理解:現在需要從房間A搬運一些紙質文件到房間B,房間A和房間B之間通過一個走廊相連。搬運文件時,先將文件放到一個文件箱中,再搬著文件箱從A房間移動到B房間。
????????在這個例子中,房間A和房間B分別是數據傳輸的起點和終點。起點和終點之間的走廊是Channel,臨時存放紙質文件的文件箱是Buffer。
????????面向緩沖的特點:
雙向的:通道可以用于讀或寫,也可以同時用于讀寫面向字節(jié)塊:程序每次可以從緩沖區(qū)中獲取一組字節(jié)數據緩沖的:緩沖中的數據可以被多次訪問任意訪問:允許在緩沖中的數據中前后移動
2.4 兩種模型的對比
????????Java BIO下的執(zhí)行流程:
????????Java NIO下的執(zhí)行流程:
3.?Java NIO API
3.1 Buffer
????????Buffer(緩沖區(qū))是Java NIO中提供的用于存儲數據的容器。Buffer底層依靠數組來存儲數據,并提供了對數據的結構化訪問以及維護讀寫位置等信息的功能。
????????Buffer只能用于存儲基本類型的數據。在NIO中,針對八種基本類型提供了7個實現類??紤]到實際過程中,數據是以字節(jié)的形式來存儲和傳輸,所以更多的使用的是ByteBuffer。
????????Buffer是一個抽象類:
????????其中定義了4個關于底層數組信息的核心屬性:
capacity:容量位,用于標記該緩沖區(qū)的容量,緩沖區(qū)創(chuàng)建好之后不可變position:操作位,用于指向要操作的位置,實際意義類似于數組中的下標,在緩沖區(qū)剛創(chuàng)建的時候指向0limit:限制位,用于限制操作位position所能達到的最大位置。在緩沖區(qū)剛創(chuàng)建的時候指向容量位mark:標記位,用于進行標記,在緩沖區(qū)剛創(chuàng)建的時候指向-1,默認不啟用
????????Buffer初始時的狀態(tài):
????????示例代碼:
????????輸出結果:
????????Buffer寫入部分數據后的狀態(tài):
????????示例代碼:
????????輸出結果:
????????Buffer反轉后的狀態(tài):
????????示例代碼:
????????輸出結果:
import java.nio.ByteBuffer;
public class BufferDemo {
public static void main(String[] args) {
//創(chuàng)建Buffer
ByteBuffer buf = ByteBuffer.allocate(10);
printBufferState(buf);
// 寫入數據到ByteBuffer
String message = "Hello NIO";
buf.put(message.getBytes());
System.out.println("Before flip():");
printBufferState(buf);
// 反轉緩沖區(qū)
buf.flip();
// 打印切換到讀模式后的狀態(tài)
System.out.println("After flip():");
printBufferState(buf);
// 讀取數據
byte[] data = new byte[buf.limit()];
buf.get(data);
// 打印讀取到的數據
System.out.println("Read data: " + new String(data));
}
public static void printBufferState(ByteBuffer buf){
//輸出3個變量的值
System.out.println("position="+buf.position()+",limit="+buf.limit()+",capacity="+buf.capacity());
}
}
3.2?Channel
????????Channel(通道)是Java NIO中提供的用于傳輸數據的工具,代表了源節(jié)點和目標節(jié)點之間的數據通道。Channel與Stream相似,但是略有不同:
Channel是雙向的Channel默認是阻塞的,可以設置為非阻塞Channel是面向緩沖區(qū)操作的
????????Java NIO中常用的Channel實現類如下:
????????其中:
FileChannel:從文件讀取數據和向文件讀取數據DatagramChannel:可以通過UDP協議在網絡上讀寫數據SocketChannel:可以通過TCP協議在網絡上讀寫數據ServerSocketChannel:允許您偵聽傳入的 TCP 連接,就像 Web 服務器一樣。 對于每個傳入連接,都會創(chuàng)建一個 SocketChannel
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelDemo {
public static void main(String[] args) throws Exception {
readDemo();
writeDemo();
}
public static void readDemo() throws Exception {
RandomAccessFile aFile = new RandomAccessFile("data/hello.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(10);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("\n====>Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get()+" ");
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
}
public static void writeDemo() throws Exception {
RandomAccessFile aFile = new RandomAccessFile("data/hello2.txt", "rw");
FileChannel channel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.wrap("Hello Channel!".getBytes());
int len=channel.write(buf);
System.out.println("len="+len);
aFile.close();
}
}
3.3?Selector
????????Selector 是 Java NIO 提供的一個組件,它可以檢查一個或多個Channel 實例,并確定哪些通道準備好用于讀、寫等操作。 通過這種方式,單個線程可以管理多個通道,從而管理多個網絡連接。
????????Selector是基于事件驅動的,供提供了4類事件:connect、accept、read和write。
????????這四類事件定義在SelectionKey中:
SelectionKey.OP_CONNECTSelectionKey.OP_ACCEPTSelectionKey.OP_READSelectionKey.OP_WRITE
????????想要使用Selector來管理Channel,需要先向Selector注冊該Channel實例,可以通過SelectableChannel.register()方法實現。
// 將channel設置為非阻塞模式
channel.configureBlocking(false);
// 將通道注冊到選擇器,并指定關注事件為讀事件
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
????????需要注意,Channel 必須處于非阻塞模式才能與 Selector 一起使用。 也就是說,不能將 FileChannel 與 Selector 一起使用,因為 FileChannel 無法切換到非阻塞模式。SocketChannel是可以與Selector搭配使用的。
????????創(chuàng)建一個ServerSocketChannel監(jiān)聽8080端口,并使用Selector來處理客戶端的連接和數據讀取。同時,創(chuàng)建了多個客戶端線程,模擬并發(fā)訪問。每個客戶端線程會連接到服務器,并發(fā)送數據,然后接收服務器的響應。
????????Server端程序代碼:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class Server {
public static void main(String[] args) throws IOException {
// 創(chuàng)建Selector
Selector selector = Selector.open();
// 創(chuàng)建ServerSocketChannel并綁定端口
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
// 將ServerSocketChannel注冊到Selector上,并指定感興趣的事件為接收連接事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started.");
while (true) {
// Selector進行事件輪詢
selector.select();
// 獲取觸發(fā)的事件集合
Set
Iterator
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
// 接收連接事件
handleAccept(key);
} else if (key.isReadable()) {
// 可讀事件
handleRead(key);
}
}
}
}
private static void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(key.selector(), SelectionKey.OP_READ);
System.out.println("Accepted new connection from: " + clientChannel.getRemoteAddress());
}
private static void handleRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) {
System.out.println("Connection closed by client: " + clientChannel.getRemoteAddress());
// 客戶端關閉連接
clientChannel.close();
} else if (bytesRead > 0) {
// 處理接收到的數據
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println("Received data from " + clientChannel.getRemoteAddress() + ": " + new String(data));
// 回寫響應數據
String response = "Response from server";
ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
clientChannel.write(responseBuffer);
}
}
}
????????Client端程序代碼:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread clientThread = new Thread(new ClientRunnable());
clientThread.start();
}
}
static class ClientRunnable implements Runnable {
@Override
public void run() {
try {
// 創(chuàng)建客戶端SocketChannel并連接到服務器
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress("localhost", 8080));
// 等待連接完成
while (!clientChannel.finishConnect()) {
Thread.sleep(100);
}
// 發(fā)送數據到服務器
String message = "Hello from client";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
clientChannel.write(buffer);
// 接收服務器響應
ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
while (clientChannel.read(responseBuffer) <= 0) {
// 等待服務器響應
Thread.sleep(100);
}
responseBuffer.flip();
byte[] responseData = new byte[responseBuffer.limit()];
responseBuffer.get(responseData);
System.out.println( clientChannel.getLocalAddress()+"=>Received response from server: " + new String(responseData));
// 關閉客戶端連接
clientChannel.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
4. 總結
????????1、阻塞(Blocking)和非阻塞(Nonblocking)描述的是線程在訪問某個資源時,在該資源沒有準備就緒期間的處理方式
阻塞:阻塞是指在進行I/O操作時,當前線程會被掛起,等待數據的就緒或操作的完成非阻塞:非阻塞是指進行I/O操作時,當前線程不會被掛起,而是立即返回并繼續(xù)執(zhí)行其他任務
????????2、同步(synchronous)與異步(asynchronous)描述的是線程在發(fā)出請求后,是否等待結果
同步是指程序按照順序執(zhí)行,并等待某個操作完成后再繼續(xù)執(zhí)行下一個操作在異步操作中,程序不會阻塞等待結果的返回,而是通過回調、輪詢或事件通知等機制來獲取結果或處理完成的事件
????????3、當涉及I/O操作時,可以根據是否阻塞和是否異步的角度將其分為以下四類
阻塞式I/O(Blocking I/O)非阻塞式I/O(Non-Blocking I/O)I/O多路復用(I/O Multiplexing)異步I/O(Asynchronous I/O)
????????4、在Java編程中提到的BIO、NIO和AIO,一般指的是Java語言為實現不同類型的I/O操作所提供的API
Java IO:Java IO是JDK 1.0版本其自帶的用于讀取和寫入數據的API,因其同步、阻塞的特征,被歸類為同步阻塞式IO(Blocking IO),即Java BIOJava NIO(New IO):是JDK 1.4開始提供的同步非阻塞式的IO操作API,因其同步、非阻塞的特征,開發(fā)者一般將Java NIO理解為Java Non-Blocking IOJava NIO 2.0:是JDK 1.7開始提供的異步非阻塞式的IO操作API,因其是在NIO的基礎上進行了改進,稱為NIO 2.0;因其異步、非阻塞的特征,開發(fā)者一般將Java NIO 2.0稱為Java AIO
????????5、Java BIO和NIO采用了2種不同的模型
BIO使用了面向流(Stream Oriented)的模型Java NIO使用了面向緩沖區(qū)(Buffer Oriented)的,基于通道(Channel Based)的模型
????????6、Java NIO編程中的核心API包括Buffer、Channel和Selector
Buffer(緩沖區(qū))是Java NIO中提供的用于存儲數據的容器,底層依靠數組來存儲數據,并提供了對數據的結構化訪問以及維護讀寫位置等信息的功能Channel(通道)是Java NIO中提供的用于傳輸數據的工具,代表了源節(jié)點和目標節(jié)點之間的數據通道Selector 是 Java NIO 提供的一個組件,它可以檢查一個或多個Channel 實例,并確定哪些通道準備好用于讀、寫等操作,通過這種方式,單個線程可以管理多個通道,從而管理多個網絡連接
柚子快報邀請碼778899分享:開發(fā)語言 Java NIO
推薦文章
本文內容根據網絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯系刪除。