Java IO三大模型(BIO/NIO/AIO)的使用總結(jié)
相信很多Java開發(fā)剛接觸IO模型時,都會被「BIO、NIO、AIO」「同步、異步、阻塞、非阻塞」這些概念繞暈,甚至把它們混為一談。其實IO模型的本質(zhì)很簡單——就是程序和外部設(shè)備(文件、網(wǎng)絡(luò)、控制臺等)之間「傳輸數(shù)據(jù)的方式」。
今天咱們不聊復(fù)雜的源碼硬啃,而是用「燒水做飯」這個生活化場景,一步步拆解所有知識點。
一、先破后立:搞懂IO的核心底層——4個關(guān)鍵概念(同步/異步、阻塞/非阻塞)
在聊Java IO三大模型之前,必須先分清「同步/異步」和「阻塞/非阻塞」這4個核心概念——它們是理解所有IO模型的基礎(chǔ),而且兩者完全是不同維度的概念,千萬別混為一談!
咱們就用最貼近生活的「燒水做飯」案例,一次性講明白,看完再也不混淆~
1. 同步 vs 異步:兩件事的「執(zhí)行順序」(是否能同時進(jìn)行)
同步和異步,描述的是「兩件獨(dú)立事情的執(zhí)行關(guān)系」——是串行執(zhí)行(先做A再做B),還是并行執(zhí)行(A和B同時做),和“等待”無關(guān),只看順序。
還是以「燒水」和「做飯」(兩件獨(dú)立的事)為例,一看就懂:
- 同步:先把水燒開,再開始做飯(串行執(zhí)行)。只有前一件事(燒水)完全做完,后一件事(做飯)才能啟動,兩件事不能同時進(jìn)行。
- 異步:把水壺放在爐子上燒著,同時就開始做飯(并行執(zhí)行)。兩件事互不干擾,不用等前一件事做完,后一件事就能啟動,最后各自完成即可。
核心記憶點:同步是「排隊做」,異步是「同時做」。
2. 阻塞 vs 非阻塞:做一件事時的「自身狀態(tài)」(遇到突發(fā)情況你在干什么)
阻塞和非阻塞,描述的是「做某一件事的過程中,遇到突發(fā)事件時,你的狀態(tài)是什么」——和“另一件事是什么”無關(guān),只和你自身的行為有關(guān)。
還是圍繞「燒水做飯」展開,這次聚焦「做飯這一件事」,遇到「燒水」這個突發(fā)事件時,你的兩種狀態(tài):
假設(shè)你正在做飯(切菜、洗菜),這是你當(dāng)前唯一的核心任務(wù);突然發(fā)現(xiàn),做飯需要熱水,得去燒一壺水(突發(fā)事件)。此時,你有兩種選擇,對應(yīng)兩種狀態(tài):
- 阻塞:把水壺放在爐子上后,什么都不做,就站在爐子旁邊盯著水燒開,期間不切菜、不洗菜——你的核心任務(wù)(做飯)暫停,全程等待突發(fā)事件(燒水)完成,這就是阻塞。
- 非阻塞:把水壺放在爐子上后,不傻等,而是回到廚房繼續(xù)切菜、洗菜(或者玩會手機(jī)),每隔一會兒去看看水開沒開——你的核心任務(wù)(做飯)沒有暫停,遇到突發(fā)事件后,你可以做其他事,不用一直等待,這就是非阻塞。
核心記憶點:阻塞是「傻等不干活」,非阻塞是「不等先干別的」。
3. 四大組合:用燒水做飯案例,吃透所有組合邏輯
理解了同步/異步(執(zhí)行順序)、阻塞/非阻塞(自身狀態(tài))后,咱們把它們組合起來,還是用「燒水做飯」的案例,逐個拆解——重點區(qū)分容易混淆的組合,尤其是“異步阻塞”為什么不存在。
(1)同步阻塞:最“笨”的方式(對應(yīng)BIO)
組合邏輯:同步(做飯必須等燒水完成,串行執(zhí)行)+ 阻塞(燒水時啥也不做,傻等水開)。
場景還原:你決定先燒水、再做飯(同步);把水壺放爐子上后,不切菜、不玩手機(jī),就站在旁邊盯著水燒開,等水完全燒開后,才開始洗菜、切菜(阻塞)。全程只有一件事在進(jìn)行,效率最低。
(2)同步非阻塞:兼顧“等待”和“效率”(對應(yīng)NIO)
組合邏輯:同步(做飯必須等燒水完成,串行執(zhí)行)+ 非阻塞(燒水時不傻等)。
場景還原:你還是要等水燒開才能做飯(同步);但把水壺放爐子上后,你不傻等,而是回到廚房切菜、洗菜(或者玩會手機(jī)),每隔1分鐘去看看水開沒開(主動檢查),等水開后,再切換到做飯的核心步驟(非阻塞)。既沒有浪費(fèi)時間,也沒有忽略突發(fā)事件。
(3)異步非阻塞:最高效的方式(對應(yīng)AIO)
組合邏輯:異步(燒水和做飯同時進(jìn)行,并行執(zhí)行)+ 非阻塞(燒水時不傻等)。
場景還原:你把水壺放爐子上燒著(不用等水開),同時就開始洗菜、切菜、做飯(兩件事并行)(異步);期間你該做飯做飯,不用專門盯著水壺,水開后會發(fā)出“鳴笛聲”通知你(被動通知),你聽到聲音后,再去關(guān)火、用熱水(非阻塞)。全程兩不耽誤,效率最高。
(4)異步阻塞:邏輯矛盾,不存在的組合
組合邏輯:異步(燒水和做飯同時進(jìn)行)+ 阻塞(啥也不做,傻等水開)。
場景還原:你把水壺放爐子上燒著,按理說可以同時做飯(異步),但你卻什么都不做,不做飯、不玩手機(jī),就站在旁邊盯著水燒開(阻塞)。這本身就很矛盾——既然選擇了“同時做兩件事”(異步),又非要“傻等其中一件事”(阻塞),最后和“同步阻塞”沒區(qū)別,完全浪費(fèi)了異步的優(yōu)勢。
所以,異步阻塞在實際場景中完全不存在,也沒有對應(yīng)的Java IO模型,咱們不用花時間糾結(jié)~
二、回歸主題:Java三大IO模型(BIO/NIO/AIO)實現(xiàn)原理+場景
搞懂了4個核心概念和組合邏輯,接下來咱們回歸Java本身——Java中的IO模型,本質(zhì)就是「同步/異步」和「阻塞/非阻塞」的不同組合,對應(yīng)三大模型:BIO(同步阻塞)、NIO(同步非阻塞)、AIO(異步非阻塞)。
咱們逐個拆解它們的實現(xiàn)原理、核心特點,結(jié)合前面的案例,讓你一看就懂,還能分清什么時候用哪個。
1. BIO:同步阻塞IO(最基礎(chǔ)、最“笨”,新手入門首選)
(1)核心定位
BIO全稱「Blocking IO」,即同步阻塞IO,是Java最早期的IO模型,對應(yīng)咱們前面說的「同步阻塞」組合——程序發(fā)起IO請求后(類似燒水),必須等待IO操作(讀/寫數(shù)據(jù))完全完成,期間線程會被掛起,什么都做不了。
(2)實現(xiàn)原理
BIO的核心是「流(Stream)」,數(shù)據(jù)傳輸是單向的(輸入流讀數(shù)據(jù)、輸出流寫數(shù)據(jù)),就像“一根單向的水管”,水只能從一端流到另一端。
實現(xiàn)邏輯很簡單:
- 程序(線程)發(fā)起IO請求(比如讀取一個文件、接收客戶端連接);
- IO操作未完成時,線程會被 操作系統(tǒng)掛起(阻塞狀態(tài)),暫停執(zhí)行,期間不能做任何其他任務(wù);
- 直到IO操作完全完成(數(shù)據(jù)讀完、連接建立),線程才會被喚醒,繼續(xù)執(zhí)行后續(xù)代碼。
(3)核心特點+對應(yīng)場景
- 優(yōu)點:編程簡單、邏輯直觀,新手容易上手,不用考慮復(fù)雜的輪詢、回調(diào),直接調(diào)用API就能實現(xiàn)IO操作。
- 缺點:效率極低、資源浪費(fèi)嚴(yán)重。一個線程只能處理一個IO請求,高并發(fā)場景下(比如1000個客戶端同時連接),需要創(chuàng)建1000個線程,線程切換和掛起會消耗大量系統(tǒng)資源,甚至導(dǎo)致程序崩潰。
- 適用場景:低并發(fā)、簡單的IO操作,比如本地小文件讀寫、簡單的控制臺交互,不需要考慮高性能(比如寫一個讀取本地文本文件的小工具)。
(4)簡單代碼示例(BIO文件讀取)
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BIOExample {
public static void main(String[] args) {
// 讀取本地文件(同步阻塞)
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
// readLine() 是阻塞方法:沒讀到數(shù)據(jù)/文件結(jié)束前,線程會一直掛起
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. NIO:同步非阻塞IO(高并發(fā)主流,性能提升關(guān)鍵)
(1)核心定位
NIO全稱「Non-blocking IO」,即同步非阻塞IO,是JDK 1.4引入的IO模型,對應(yīng)咱們前面說的「同步非阻塞」組合——程序發(fā)起IO請求后,不用等待IO操作完成,線程可以去做其他任務(wù),期間主動輪詢檢查IO操作是否完成。
它解決了BIO高并發(fā)下的資源浪費(fèi)問題,是目前Java高并發(fā)網(wǎng)絡(luò)編程的主流(比如Netty框架,就是基于NIO封裝的)。
(2)實現(xiàn)原理
NIO的核心不再是“流”,而是「通道(Channel)+ 緩沖區(qū)(Buffer)+ 選擇器(Selector)」,三者協(xié)同工作,實現(xiàn)“單線程處理多個IO請求”,也就是咱們常說的「IO多路復(fù)用」。
拆解三個核心組件,通俗易懂:
- 通道(Channel):雙向的“水管”,替代BIO的單向流,數(shù)據(jù)可以雙向傳輸(既能讀又能寫),比如文件通道(FileChannel)、網(wǎng)絡(luò)通道(SocketChannel)。
- 緩沖區(qū)(Buffer):數(shù)據(jù)的“容器”,所有IO操作都通過Buffer完成(讀數(shù)據(jù):從通道讀到緩沖區(qū);寫數(shù)據(jù):從緩沖區(qū)寫到通道),避免頻繁操作底層資源,提升效率。
- 選擇器(Selector):NIO的核心,相當(dāng)于“一個調(diào)度員”。單線程可以通過Selector,同時管理多個通道,監(jiān)聽通道上的IO事件(比如“有數(shù)據(jù)可讀”“有客戶端連接”);線程不用阻塞等待每個通道的IO完成,而是由Selector通知線程“哪個通道的IO準(zhǔn)備好了”,線程再去處理對應(yīng)的通道。
實現(xiàn)邏輯總結(jié):
- 創(chuàng)建通道,并設(shè)置為非阻塞模式;
- 將所有通道注冊到Selector上,指定要監(jiān)聽的IO事件;
- 線程通過Selector輪詢(非阻塞),檢查是否有通道的IO事件就緒;
- 如果有就緒的通道,線程就去處理對應(yīng)的IO操作(讀/寫數(shù)據(jù));如果沒有,線程可以去做其他任務(wù),過會兒再輪詢。
(3)核心特點+對應(yīng)場景
- 優(yōu)點:高并發(fā)性能優(yōu)秀,單線程可處理多個IO請求,減少線程創(chuàng)建和切換的開銷;資源利用率高,線程不用傻等,可并行處理其他任務(wù)。
- 缺點:編程復(fù)雜度比BIO高,需要理解Channel、Buffer、Selector的協(xié)同工作機(jī)制;輪詢操作會消耗一定的CPU資源(但比BIO的線程阻塞好得多)。
- 適用場景:高并發(fā)網(wǎng)絡(luò)編程,比如服務(wù)器、網(wǎng)關(guān)、聊天 服務(wù)器等,需要處理大量連接,但每個連接的數(shù)據(jù)量不大的場景。
(4)簡單代碼示例(NIO Socket 服務(wù)端,多路復(fù)用)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOExample {
public static void main(String[] args) throws IOException {
// 1. 創(chuàng)建 ServerSocketChannel 并設(shè)置為非阻塞
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8888));
serverChannel.configureBlocking(false); // 關(guān)鍵:設(shè)置非阻塞
// 2. 創(chuàng)建 Selector(選擇器)
Selector selector = Selector.open();
// 3. 將 ServerSocketChannel 注冊到 Selector,監(jiān)聽“連接事件”
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("NIO 服務(wù)端啟動,監(jiān)聽端口:8888");
while (true) {
// 4. 輪詢就緒的 Channel(阻塞,直到有事件發(fā)生)
selector.select();
// 5. 獲取所有就緒的 SelectionKey
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove(); // 必須移除,避免重復(fù)處理
// 6. 處理不同事件
if (key.isAcceptable()) {
// 處理連接事件
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept(); // 非阻塞,不會掛起
sc.configureBlocking(false);
// 注冊讀事件
sc.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
System.out.println("客戶端連接成功:" + sc.getRemoteAddress());
} else if (key.isReadable()) {
// 處理讀事件
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
int read = sc.read(buffer); // 非阻塞,返回讀取的字節(jié)數(shù)
if (read > 0) {
buffer.flip();
String msg = new String(buffer.array(), 0, buffer.limit());
System.out.println("收到客戶端消息:" + msg);
buffer.clear();
} else if (read < 0) {
// 客戶端斷開連接
sc.close();
key.cancel();
System.out.println("客戶端斷開連接:" + sc.getRemoteAddress());
}
}
}
}
}
}
3. AIO:異步非阻塞IO(高性能天花板,場景受限)
(1)核心定位
AIO全稱「Asynchronous IO」,即異步非阻塞IO,是JDK 1.7引入的IO模型(也叫NIO.2),對應(yīng)咱們前面說的「異步非阻塞」組合——程序發(fā)起IO請求后,直接返回,不用等待、不用輪詢,IO操作由操作系統(tǒng)異步完成;當(dāng)IO操作完成后,操作系統(tǒng)會通過「回調(diào)函數(shù)」或「Future」通知程序,程序再去處理結(jié)果。
它是三者中效率最高的,但受操作系統(tǒng)支持限制,實際應(yīng)用不如NIO廣泛。
(2)實現(xiàn)原理
AIO的核心是「異步回調(diào)」,程序完全不用關(guān)心IO操作的過程,只需要發(fā)起請求、注冊回調(diào),剩下的都交給操作系統(tǒng)處理,相當(dāng)于“甩手掌柜”。
實現(xiàn)邏輯很簡單,分為3步:
- 程序(線程)發(fā)起IO請求(比如讀取大文件、接收客戶端連接),并注冊一個「回調(diào)函數(shù)」;
- 請求發(fā)起后,線程立即返回,繼續(xù)執(zhí)行其他任務(wù)(非阻塞),IO操作由操作系統(tǒng)在后臺異步完成;
- 當(dāng)IO操作完成(數(shù)據(jù)讀完、連接建立),操作系統(tǒng)會自動調(diào)用注冊的回調(diào)函數(shù),通知程序“IO操作完成”,程序再在回調(diào)函數(shù)中處理返回的數(shù)據(jù)。
關(guān)鍵區(qū)別:NIO是「程序主動輪詢」檢查IO是否完成,而AIO是「操作系統(tǒng)被動通知」,程序不用做任何等待和輪詢,資源利用率達(dá)到最高。
(3)核心特點+對應(yīng)場景
- 優(yōu)點:性能最優(yōu),全程非阻塞,線程不用等待、不用輪詢,完全解放線程;適合處理大數(shù)據(jù)量、高延遲的IO操作。
- 缺點:編程復(fù)雜度最高,需要理解異步回調(diào)、Future等機(jī)制;受操作系統(tǒng)支持限制(Windows系統(tǒng)對AIO支持較好,Linux系統(tǒng)對AIO支持有限,實際開發(fā)中常用NIO替代)。
- 適用場景:高并發(fā)、大數(shù)據(jù)量、低延遲的IO場景,比如大文件傳輸、視頻流處理、高性能服務(wù)器等,且操作系統(tǒng)支持異步IO的場景。
(4)簡單代碼示例(AIO異步回調(diào))
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
public class AIOExample {
public static void main(String[] args) throws IOException {
// 1. 創(chuàng)建異步服務(wù)端通道
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8888));
System.out.println("AIO服務(wù)端啟動,監(jiān)聽端口8888...");
// 2. 異步接受連接(注冊回調(diào)函數(shù),IO完成后自動觸發(fā))
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel sc, Void attachment) {
// 繼續(xù)接受下一個客戶端連接(否則只能處理一個客戶端)
serverChannel.accept(null, this);
System.out.println("客戶端連接成功:" + sc);
// 3. 異步讀取客戶端數(shù)據(jù)(注冊回調(diào))
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer readBytes, ByteBuffer buf) {
// IO操作完成(讀取到數(shù)據(jù)),處理數(shù)據(jù)
if (readBytes > 0) {
buf.flip();
String msg = new String(buf.array(), 0, readBytes);
System.out.println("收到客戶端消息:" + msg);
// 繼續(xù)讀取下一次數(shù)據(jù)
sc.read(buf, buf, this);
}
}
@Override
public void failed(Throwable exc, ByteBuffer buf) {
// IO操作失敗,處理異常
exc.printStackTrace();
}
});
}
@Override
public void failed(Throwable exc, Void attachment) {
// 連接失敗,處理異常
exc.printStackTrace();
}
});
// 防止主線程退出(主線程不用處理IO,只負(fù)責(zé)發(fā)起請求)
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三、總結(jié):三大IO模型核心對比(一眼分清,快速選型)
學(xué)到這里,相信你已經(jīng)吃透了Java IO三大模型和核心概念。最后咱們用一張表格,匯總?cè)叩暮诵膮^(qū)別,幫你快速選型,避免踩坑:
| IO模型 | 類型 | 核心優(yōu)勢 | 核心劣勢 | 適用場景 |
|---|---|---|---|---|
| BIO | 同步阻塞 | 編程簡單、邏輯直觀 | 效率低、資源浪費(fèi),不支持高并發(fā) | 低并發(fā)、小文件讀寫、簡單交互 |
| NIO | 同步非阻塞 | 高并發(fā)性能優(yōu)秀,資源利用率高 | 編程復(fù)雜度中等,有輪詢開銷 | 高并發(fā)網(wǎng)絡(luò)編程(服務(wù)器、網(wǎng)關(guān)等) |
| AIO | 異步非阻塞 | 性能最優(yōu),完全解放線程 | 編程復(fù)雜,受操作系統(tǒng)支持限制 | 大數(shù)據(jù)量、低延遲場景(大文件傳輸?shù)龋?/td> |
到此這篇關(guān)于Java IO三大模型(BIO/NIO/AIO)的使用總結(jié)的文章就介紹到這了,更多相關(guān)Java IO模型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA?2021.3?使用及idea2021.3.1激活使用方法
IDEA?全稱?IntelliJ?IDEA,是java語言開發(fā)的集成環(huán)境,IntelliJ在業(yè)界被公認(rèn)為最好的java開發(fā)工具之一,今天通過本文給大家介紹idea2021.3.1激活及使用教程,感興趣的朋友一起看看吧2022-01-01
springboot添加多數(shù)據(jù)源的方法實例教程
這篇文章主要給大家介紹了關(guān)于springboot添加多數(shù)據(jù)源方法的相關(guān)資料,在實際開發(fā)中經(jīng)??赡苡龅皆谝粋€應(yīng)用中可能要訪問多個數(shù)據(jù)庫多的情況,需要的朋友可以參考下2023-09-09
MyBatisPlus+SpringBoot實現(xiàn)樂觀鎖功能詳細(xì)流程
樂觀鎖是針對一些特定問題的解決方案,主要解決丟失更新問題,下面這篇文章主要給大家介紹了關(guān)于MyBatisPlus+SpringBoot實現(xiàn)樂觀鎖功能的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03
java實現(xiàn)拉鉤網(wǎng)上的FizzBuzzWhizz問題示例
這篇文章主要介紹了java實現(xiàn)拉鉤網(wǎng)上的FizzBuzzWhizz問題示例,需要的朋友可以參考下2014-05-05
使用Java servlet實現(xiàn)自動登錄退出功能
這篇文章主要介紹了使用Java servlet實現(xiàn)自動登錄退出功能,,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-11-11

