最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中IO多路復(fù)用技術(shù)的用法解讀

 更新時(shí)間:2025年11月11日 09:14:54   作者:探索java  
本文詳細(xì)介紹了Java中的IO多路復(fù)用機(jī)制,包括阻塞IO、非阻塞IO(NIO)和異步IO(AIO)的不同,并重點(diǎn)講解了Java?NIO的核心組件:通道(Channel)、緩沖區(qū)(Buffer)和選擇器(Selector),通過實(shí)際代碼示例,展示了如何使用NIO實(shí)現(xiàn)高效的多客戶端服務(wù)器

1. 引言:IO多路復(fù)用的概念和重要性

在網(wǎng)絡(luò)編程中,高并發(fā)場(chǎng)景往往需要處理成千上萬的客戶端連接請(qǐng)求。傳統(tǒng)的阻塞IO模型(BIO)使用線程綁定一個(gè)連接的方式,難以應(yīng)對(duì)大量并發(fā)連接,資源浪費(fèi)嚴(yán)重、擴(kuò)展性差。

IO多路復(fù)用(I/O Multiplexing)是一種操作系統(tǒng)層面的機(jī)制,允許程序使用一個(gè)或少量的線程同時(shí)監(jiān)聽多個(gè)IO通道(Socket/File等),通過事件通知機(jī)制在數(shù)據(jù)準(zhǔn)備就緒時(shí)處理操作,極大地提升了系統(tǒng)的并發(fā)能力和資源利用率。

Java從1.4版本引入NIO(New IO)庫,提供了非阻塞IO編程模型,利用Selector+Channel實(shí)現(xiàn)IO多路復(fù)用,從根本上解決了傳統(tǒng)阻塞IO的瓶頸問題。Java NIO的出現(xiàn)標(biāo)志著Java正式邁入高性能IO時(shí)代,為后續(xù)如Netty等高性能網(wǎng)絡(luò)框架奠定了基礎(chǔ)。

1.1 IO多路復(fù)用的核心思想

  • 使用單個(gè)線程輪詢多個(gè)IO事件,避免線程頻繁創(chuàng)建和上下文切換。

  • 通過事件驅(qū)動(dòng)(例如:可讀、可寫、連接完成等)判斷Channel是否可操作。

  • 通常與非阻塞IO結(jié)合使用,配合Selector機(jī)制進(jìn)行高效輪詢。

1.2 IO多路復(fù)用的優(yōu)勢(shì)

  • 資源占用低:極少的線程處理大量連接。

  • 高吞吐量:避免線程阻塞,提升響應(yīng)速度。

  • 可擴(kuò)展性強(qiáng):適用于成千上萬連接的服務(wù)器模型。

  • 事件驅(qū)動(dòng)設(shè)計(jì):與回調(diào)/異步框架自然契合。

1.3 與傳統(tǒng)IO模型的比較

特性阻塞IO(BIO)非阻塞IO(NIO)異步IO(AIO)
線程模型一線程/連接一線程/多連接操作系統(tǒng)管理IO
并發(fā)能力非常好
編程復(fù)雜度
性能表現(xiàn)很高(受限于平臺(tái)支持)

隨著互聯(lián)網(wǎng)應(yīng)用的高速發(fā)展,傳統(tǒng)BIO模型已經(jīng)無法滿足高并發(fā)的場(chǎng)景需求,而NIO和AIO則提供了高性能、高并發(fā)的解決方案,尤其是NIO因其良好的跨平臺(tái)兼容性和成熟度,在Java領(lǐng)域被廣泛應(yīng)用。

2. Java中的IO模型

Java的IO模型決定了數(shù)據(jù)在應(yīng)用程序與外部設(shè)備(如磁盤、網(wǎng)絡(luò))之間傳輸?shù)姆绞?。理解不同的IO模型是掌握IO多路復(fù)用的基礎(chǔ)。本節(jié)將系統(tǒng)介紹Java支持的三種主要IO模型:阻塞IO(BIO)、非阻塞IO(NIO)和異步IO(AIO)。

2.1 阻塞IO(BIO)

阻塞IO是Java最傳統(tǒng)、最早的IO模型,其核心特點(diǎn)是:讀寫操作會(huì)阻塞線程,直到操作完成。

原理說明

  • 每個(gè)客戶端連接由一個(gè)獨(dú)立線程負(fù)責(zé)處理。

  • 調(diào)用InputStream.read()OutputStream.write()時(shí),線程會(huì)阻塞,直到數(shù)據(jù)可用或?qū)懭胪瓿伞?/p>

示例代碼:經(jīng)典的BIO服務(wù)器

public class BioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("BIO服務(wù)器啟動(dòng),端口:8080");
        
        while (true) {
            Socket clientSocket = serverSocket.accept(); // 阻塞
            new Thread(() -> handle(clientSocket)).start();
        }
    }

    private static void handle(Socket socket) {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
             BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(socket.getOutputStream()))) {

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("收到消息: " + line);
                writer.write("Echo: " + line + "\n");
                writer.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BIO存在的問題

  • 線程資源開銷大:每個(gè)連接占用一個(gè)線程。

  • 擴(kuò)展性差:連接數(shù)增加時(shí),線程數(shù)量飆升,造成資源浪費(fèi)。

2.2 非阻塞IO(NIO)

Java NIO引入于Java 1.4,基于Channel、Buffer和Selector實(shí)現(xiàn)了非阻塞IO和IO多路復(fù)用。

原理說明

  • Channel是雙向的,既可以讀取也可以寫入。

  • 通過Selector一個(gè)線程可管理多個(gè)Channel的事件(如可讀、可寫)。

  • IO操作不會(huì)阻塞線程,讀取或?qū)懭肴粑赐瓿蓜t立即返回。

非阻塞模式設(shè)置

SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false); // 設(shè)置為非阻塞

示例代碼:簡(jiǎn)單的NIO服務(wù)端(略,詳見第7章)

優(yōu)勢(shì):

  • 大量連接只需少量線程管理。

  • 提升服務(wù)器的并發(fā)性能。

  • 實(shí)現(xiàn)復(fù)雜但性能優(yōu)于BIO。

2.3 異步IO(AIO)

異步IO是Java 7引入的新特性,又稱為NIO.2。它完全由操作系統(tǒng)負(fù)責(zé)通知IO事件完成,通過回調(diào)處理IO結(jié)果。

原理說明

  • 發(fā)起IO操作后,立即返回;無需等待數(shù)據(jù)傳輸完成。

  • 操作系統(tǒng)在IO完成后主動(dòng)回調(diào)通知應(yīng)用層。

關(guān)鍵類

  • AsynchronousSocketChannel

  • CompletionHandler

示例代碼:異步讀取示意

AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("異步讀取完成: " + result);
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        System.err.println("讀取失敗: " + exc.getMessage());
    }
});

特點(diǎn)

  • 編程復(fù)雜,邏輯解耦困難(大量回調(diào))。

  • 最適合IO密集、長連接、高延遲場(chǎng)景。

  • 依賴操作系統(tǒng)底層AIO支持(在Linux和Windows表現(xiàn)差異較大)。

2.4 小結(jié)

模型是否阻塞并發(fā)性開發(fā)復(fù)雜度適用場(chǎng)景
BIO阻塞簡(jiǎn)單低并發(fā)、教學(xué)、小型項(xiàng)目
NIO非阻塞較好中等中高并發(fā)服務(wù)、聊天系統(tǒng)、游戲服務(wù)器
AIO異步非常好較高高并發(fā)、大吞吐、長連接系統(tǒng)

理解這些IO模型的差異,有助于在不同業(yè)務(wù)場(chǎng)景下合理選擇技術(shù)方案。在接下來的章節(jié)中,我們將系統(tǒng)講解Java NIO的核心架構(gòu)及各組成部分。

3. Java NIO概述

Java NIO(New I/O)是Java在1.4版本引入的全新IO庫,相較于傳統(tǒng)的BIO(Blocking IO)模型,NIO引入了基于緩沖區(qū)(Buffer)、**通道(Channel)選擇器(Selector)**的異步IO處理機(jī)制,從而使得Java可以更高效地處理大量并發(fā)連接和IO密集型操作。

本節(jié)將詳細(xì)剖析java.nio包結(jié)構(gòu),以及NIO模型相較于BIO模型的關(guān)鍵差異,為后續(xù)深入學(xué)習(xí)Selector與多路復(fù)用機(jī)制打下基礎(chǔ)。

3.1 java.nio包結(jié)構(gòu)

NIO的類被組織在如下幾個(gè)核心包中:

包名描述
java.nioBuffer抽象及基礎(chǔ)實(shí)現(xiàn)
java.nio.channelsChannel接口及Socket、File等通道實(shí)現(xiàn)
java.nio.channels.spi通道與Selector的服務(wù)提供接口(SPI)
java.nio.charset字符集轉(zhuǎn)換支持(Charset)
java.nio.file(Java 7+)對(duì)文件路徑、目錄、權(quán)限等的增強(qiáng)支持
java.nio.file.attribute文件屬性操作

常見類與接口速查表

類/接口描述
Buffer所有緩沖區(qū)的抽象父類
ByteBuffer, CharBuffer, ...用于存儲(chǔ)各種原始數(shù)據(jù)類型的緩沖區(qū)實(shí)現(xiàn)
Channel表示IO通道的頂層接口
FileChannel, SocketChannel文件/套接字通道實(shí)現(xiàn)
Selector多路復(fù)用器,監(jiān)聽多個(gè)通道上的事件
SelectionKey描述Selector與Channel之間的關(guān)系及感興趣的事件
Charset字符編碼及解碼器

這些類和接口共同構(gòu)成了NIO的三大核心組件:Buffer、Channel 和 Selector,它們密切配合實(shí)現(xiàn)高效IO處理。

3.2 NIO與BIO的根本區(qū)別

NIO不僅僅是API層面的變化,更是IO編程模型的根本變革。以下從多個(gè)角度對(duì)比兩者的差異:

1. IO處理模式

  • BIO是**面向流(Stream-Oriented)**的,每次IO操作都像一股流一樣從源到目標(biāo)順序傳輸。

  • NIO是**面向緩沖區(qū)(Buffer-Oriented)**的,數(shù)據(jù)先讀入緩沖區(qū),再從緩沖區(qū)處理,提升了靈活性與效率。

2. 同步阻塞與非阻塞

  • BIO每個(gè)線程阻塞處理一個(gè)連接。

  • NIO支持非阻塞模式,使用Selector輪詢多個(gè)Channel的狀態(tài)變化。

3. 多路復(fù)用能力

  • BIO無法復(fù)用線程資源。

  • NIO通過Selector實(shí)現(xiàn)了一個(gè)線程處理多個(gè)連接的能力(IO多路復(fù)用)。

4. 數(shù)據(jù)操作方式

  • BIO通過字節(jié)流(InputStream/OutputStream)處理數(shù)據(jù),順序固定,且缺乏靈活性。

  • NIO通過ByteBuffer等操作塊狀數(shù)據(jù),支持隨機(jī)訪問、回退、標(biāo)記等特性。

5. 系統(tǒng)資源消耗

  • BIO每個(gè)連接需線程支持,線程上下文切換成本高。

  • NIO大量連接復(fù)用少量線程,資源消耗顯著降低。

示例對(duì)比(BIO vs NIO 接收數(shù)據(jù))

BIO讀取數(shù)據(jù)

InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = in.read(buffer); // 可能阻塞

NIO讀取數(shù)據(jù)

ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel channel = socket.getChannel();
int len = channel.read(buffer); // 非阻塞

4. Channel詳解

在Java NIO中,Channel(通道)是數(shù)據(jù)傳輸?shù)暮诵慕M件,類似于BIO中的流(Stream),但它具有雙向讀寫能力,并且支持異步非阻塞操作,是實(shí)現(xiàn)IO多路復(fù)用的基礎(chǔ)之一。

本節(jié)將系統(tǒng)介紹Channel的基礎(chǔ)概念、通道的分類及其使用方式,包括SocketChannel、ServerSocketChannel、DatagramChannel和FileChannel。

4.1 Channel的基本概念

什么是Channel?

Channel是Java NIO中用于數(shù)據(jù)讀取和寫入的對(duì)象。它表示一種可以讀取或?qū)懭霐?shù)據(jù)的通道,通常與底層的硬件設(shè)備(如文件、網(wǎng)絡(luò)套接字)進(jìn)行交互。

與傳統(tǒng)IO中的InputStream/OutputStream不同,Channel具備以下特點(diǎn):

  • 雙向:既可以讀也可以寫。

  • 支持非阻塞模式:可配合Selector進(jìn)行IO多路復(fù)用。

  • 基于Buffer進(jìn)行讀寫:所有數(shù)據(jù)操作都需借助Buffer中轉(zhuǎn)。

Channel接口體系結(jié)構(gòu)

java.nio.channels.Channel
 ├── ReadableByteChannel
 ├── WritableByteChannel
 ├── ByteChannel
 ├── NetworkChannel
 └── InterruptibleChannel

4.2 常用Channel類型詳解

1. FileChannel(文件通道)

用于讀取、寫入、映射和操作文件內(nèi)容。

創(chuàng)建方式:通過FileInputStream、FileOutputStream或RandomAccessFile獲取。

FileChannel fileChannel = new FileInputStream("data.txt").getChannel();

特性

  • 支持隨機(jī)讀寫(position)。

  • 支持文件鎖、內(nèi)存映射(MappedByteBuffer)。

  • 不支持非阻塞模式。

2. SocketChannel(TCP客戶端通道)

用于創(chuàng)建客戶端TCP連接,支持非阻塞模式。

  • 創(chuàng)建方式

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8080));
  • 設(shè)置非阻塞

socketChannel.configureBlocking(false);
  • 讀寫操作

ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.write(buffer);
socketChannel.read(buffer);
  • 適用場(chǎng)景:客戶端發(fā)起連接、發(fā)送請(qǐng)求、接收響應(yīng)。

3. ServerSocketChannel(TCP服務(wù)器通道)

用于監(jiān)聽TCP連接請(qǐng)求,是服務(wù)端的入口通道。

  • 創(chuàng)建方式

ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
  • 非阻塞監(jiān)聽與接受連接

serverChannel.configureBlocking(false);
SocketChannel client = serverChannel.accept(); // 非阻塞可能返回null
  • 與Selector配合監(jiān)聽連接請(qǐng)求。

4. DatagramChannel(UDP通道)

用于UDP協(xié)議的數(shù)據(jù)發(fā)送與接收。

  • 創(chuàng)建方式

DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.bind(new InetSocketAddress(8888));
  • 接收與發(fā)送

ByteBuffer buffer = ByteBuffer.allocate(1024);
datagramChannel.receive(buffer);
datagramChannel.send(buffer, new InetSocketAddress("localhost", 9999));
  • 可設(shè)為非阻塞模式,配合Selector使用。

4.3 Channel使用注意事項(xiàng)

  • Channel必須與Buffer配合使用,不能直接讀寫原始數(shù)據(jù)。

  • 非阻塞通道在數(shù)據(jù)未就緒時(shí)返回0或null,而非阻塞等待。

  • FileChannel不支持Selector,不能用于IO多路復(fù)用。

  • 網(wǎng)絡(luò)通道關(guān)閉后必須釋放資源,避免內(nèi)存泄漏。

5. Buffer詳解

在Java NIO中,Buffer(緩沖區(qū))是Channel數(shù)據(jù)讀寫的中介核心,承擔(dān)著存儲(chǔ)和傳輸數(shù)據(jù)的關(guān)鍵職責(zé)。NIO中所有的讀寫操作都必須依賴Buffer完成。因此,深入理解Buffer的結(jié)構(gòu)與使用方式,是掌握J(rèn)ava NIO編程的基礎(chǔ)。

本節(jié)將系統(tǒng)講解Buffer的基本原理、常見類型、直接緩沖區(qū)與非直接緩沖區(qū)的區(qū)別,以及Buffer的基本操作方法,輔以代碼示例確保理解。

5.1 Buffer的基本原理

Buffer是什么?

Buffer本質(zhì)上是一個(gè)封裝了固定容量數(shù)組的容器對(duì)象,用于臨時(shí)存儲(chǔ)數(shù)據(jù)以供Channel讀寫操作。

每個(gè)Buffer都具備如下四個(gè)核心屬性:

  • capacity:容量,即緩沖區(qū)最大可容納的數(shù)據(jù)量(單位為字節(jié)或元素?cái)?shù))。

  • position:當(dāng)前位置,指明下一次讀取或?qū)懭氲奈恢谩?/p>

  • limit:限制位置,表示當(dāng)前操作的最大數(shù)據(jù)邊界。

  • mark:標(biāo)記,可通過mark()設(shè)置,用于后續(xù)reset()返回此位置。

Buffer的工作流程

Buffer的使用通常包含四個(gè)階段:

  • 寫入數(shù)據(jù)到Buffer(從通道或手動(dòng)put)
  • 調(diào)用flip()方法切換為讀模式
  • 讀取數(shù)據(jù)(get操作)
  • **調(diào)用clear()或compact()**準(zhǔn)備下一次寫入

示例代碼:基本使用流程

ByteBuffer buffer = ByteBuffer.allocate(1024); // 創(chuàng)建非直接緩沖區(qū)
buffer.put("Hello NIO".getBytes());           // 寫入數(shù)據(jù)
buffer.flip();                                // 切換為讀模式

while (buffer.hasRemaining()) {
    System.out.print((char) buffer.get());    // 讀取數(shù)據(jù)
}

buffer.clear();                               // 清空緩沖區(qū)準(zhǔn)備再次寫入

5.2 Buffer的類型與作用

Java NIO提供了多種類型的Buffer以支持不同數(shù)據(jù)類型的讀寫:

類型描述
ByteBuffer處理字節(jié)數(shù)據(jù),最常用
CharBuffer處理char字符數(shù)據(jù)
IntBuffer處理int整型數(shù)據(jù)
LongBuffer處理long類型數(shù)據(jù)
FloatBuffer處理float類型數(shù)據(jù)
DoubleBuffer處理double數(shù)據(jù)
ShortBuffer處理short類型數(shù)據(jù)

這些Buffer都繼承自抽象類Buffer,其核心使用方式基本一致,只是數(shù)據(jù)類型不同。

示例:使用IntBuffer

IntBuffer intBuffer = IntBuffer.allocate(5);
intBuffer.put(10);
intBuffer.put(20);
intBuffer.flip();
System.out.println(intBuffer.get()); // 輸出10

5.3 直接緩沖區(qū)與非直接緩沖區(qū)

Java NIO中的ByteBuffer可分為兩類:

非直接緩沖區(qū)(Heap Buffer)

  • 使用ByteBuffer.allocate(capacity)創(chuàng)建。

  • 數(shù)據(jù)保存在JVM的堆內(nèi)存中。

  • 分配速度快,但I(xiàn)O操作需多次拷貝(用戶空間 <-> 內(nèi)核空間)。

直接緩沖區(qū)(Direct Buffer)

  • 使用ByteBuffer.allocateDirect(capacity)創(chuàng)建。

  • 數(shù)據(jù)分配在操作系統(tǒng)的直接內(nèi)存(off-heap)中。

  • 讀寫操作可直接與通道交互,性能優(yōu)于非直接緩沖區(qū)。

  • 分配代價(jià)高,管理成本大(GC不可直接回收)。

示例:創(chuàng)建直接緩沖區(qū)

ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
directBuffer.put("Netty Rocks".getBytes());
directBuffer.flip();

如何選擇?

  • 頻繁分配/釋放內(nèi)存場(chǎng)景:使用非直接緩沖區(qū)。

  • 大規(guī)模IO傳輸/性能敏感系統(tǒng):使用直接緩沖區(qū)提升吞吐率。

5.4 Buffer的核心方法

方法描述
put()寫入數(shù)據(jù)到緩沖區(qū)
get()從緩沖區(qū)讀取數(shù)據(jù)
flip()寫模式切換為讀模式
clear()清空緩沖區(qū),重置position和limit
compact()清除已讀數(shù)據(jù),保留未讀數(shù)據(jù)
rewind()重置position為0,重新讀取
mark() / reset()標(biāo)記和重置position位置

示例:compact()使用場(chǎng)景

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("abc".getBytes());
buffer.flip();
System.out.println((char) buffer.get()); // 讀取a
buffer.compact(); // b和c移動(dòng)到緩沖區(qū)前端,準(zhǔn)備繼續(xù)寫入

5.5 注意事項(xiàng)與最佳實(shí)踐

  • 調(diào)用flip()后才能讀取數(shù)據(jù),否則position未歸零導(dǎo)致讀取為空。

  • 多線程中使用Buffer時(shí)需避免線程共享或加鎖。

  • 直接緩沖區(qū)使用完后不可立即GC,長期不清理可能造成內(nèi)存泄露。

  • Buffer容量一經(jīng)分配無法動(dòng)態(tài)擴(kuò)展,需提前估算使用量。

6. Selector詳解:多路復(fù)用器的工作原理與使用方法

Selector(選擇器)是Java NIO實(shí)現(xiàn)IO多路復(fù)用的核心組件。它允許單線程同時(shí)監(jiān)控多個(gè)通道的事件(如連接、讀取、寫入等),大大提高了系統(tǒng)資源利用率,是高性能網(wǎng)絡(luò)服務(wù)器的基石。

本節(jié)將詳細(xì)介紹Selector的工作機(jī)制、相關(guān)類、注冊(cè)與監(jiān)聽過程、事件處理流程,并輔以完整示例代碼說明。

6.1 什么是Selector?

Selector是Java NIO中用于監(jiān)聽多個(gè)通道事件的工具類,它可以注冊(cè)多個(gè)通道,并在這些通道上監(jiān)聽各種事件,一旦事件就緒,就可以觸發(fā)處理。

為什么需要Selector?

在傳統(tǒng)阻塞IO中,每個(gè)連接都需要一個(gè)線程處理,如果有成千上萬個(gè)連接,線程資源消耗巨大。而Selector允許一個(gè)線程處理多個(gè)連接,極大提升了IO性能與可擴(kuò)展性。

核心原理:

  • 每個(gè)Channel都可以注冊(cè)到Selector上。

  • Channel與Selector之間通過SelectionKey關(guān)聯(lián)。

  • 當(dāng)某個(gè)Channel有事件準(zhǔn)備就緒,Selector會(huì)將其標(biāo)記并返回。

6.2 Selector相關(guān)類與接口

  • Selector:選擇器類,是事件監(jiān)聽的入口。

  • SelectableChannel:所有可注冊(cè)到Selector的Channel,如SocketChannel。

  • SelectionKey:通道與Selector之間的橋梁,保存感興趣的事件類型及通道狀態(tài)。

SelectionKey的四種操作事件常量

SelectionKey.OP_CONNECT  // 客戶端連接就緒
SelectionKey.OP_ACCEPT   // 服務(wù)器接收連接就緒
SelectionKey.OP_READ     // 讀就緒
SelectionKey.OP_WRITE    // 寫就緒

6.3 Selector的創(chuàng)建與通道注冊(cè)

創(chuàng)建Selector

Selector selector = Selector.open();

配置通道為非阻塞并注冊(cè)事件

SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);

注冊(cè)多個(gè)事件類型

socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

6.4 Selector的工作流程詳解

Selector的核心工作方式包括三個(gè)步驟:

1. 輪詢就緒通道

int readyChannels = selector.select();
  • select():阻塞直到有通道就緒。

  • select(timeout):指定最大阻塞時(shí)間。

  • selectNow():非阻塞立即返回。

2. 獲取就緒通道集合

Set<SelectionKey> selectedKeys = selector.selectedKeys();

3. 迭代處理事件

for (SelectionKey key : selectedKeys) {
    if (key.isAcceptable()) {
        // 處理連接請(qǐng)求
    } else if (key.isReadable()) {
        // 讀取數(shù)據(jù)
    } else if (key.isWritable()) {
        // 寫入數(shù)據(jù)
    }
}
selectedKeys.clear(); // 處理完需清除集合

6.5 完整示例:Selector處理多通道讀寫

Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = keys.iterator();

    while (iterator.hasNext()) {
        SelectionKey key = iterator.next();

        if (key.isAcceptable()) {
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int read = client.read(buffer);
            if (read > 0) {
                buffer.flip();
                client.write(buffer);
                buffer.clear();
            }
        }
        iterator.remove(); // 防止重復(fù)處理
    }
}

6.6 Selector使用注意事項(xiàng)

  • Selector是線程安全的,但通常不建議多線程共享。

  • 必須在非阻塞通道上使用Selector,否則會(huì)拋出異常。

  • 處理完事件后務(wù)必調(diào)用selectedKeys().clear(),否則可能導(dǎo)致事件重復(fù)處理。

  • 調(diào)用cancel()方法可取消某個(gè)通道的注冊(cè)。

7. 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的NIO服務(wù)器

本章將基于前文介紹的核心組件(Channel、Buffer、Selector),構(gòu)建一個(gè)最小可運(yùn)行的非阻塞NIO服務(wù)器,能夠接受客戶端連接、讀取消息并原樣返回(Echo服務(wù))。

該服務(wù)器具備以下功能:

  • 非阻塞監(jiān)聽指定端口

  • 利用Selector管理多個(gè)客戶端連接

  • 使用ByteBuffer實(shí)現(xiàn)數(shù)據(jù)讀取與寫入

  • 支持多個(gè)客戶端并發(fā)連接處理

通過本章,讀者將徹底掌握NIO服務(wù)端編程的基本結(jié)構(gòu),為后續(xù)高階特性(如多線程處理、協(xié)議解析等)打下堅(jiān)實(shí)基礎(chǔ)。

7.1 構(gòu)建步驟概覽

構(gòu)建一個(gè)NIO服務(wù)端大致包括以下步驟:

  1. 打開并配置ServerSocketChannel為非阻塞

  2. 綁定端口并注冊(cè)到Selector監(jiān)聽ACCEPT事件

  3. 循環(huán)輪詢Selector監(jiān)聽事件

  4. 接收客戶端連接并注冊(cè)其Channel到Selector,監(jiān)聽READ事件

  5. 當(dāng)有可讀事件時(shí),讀取數(shù)據(jù)并寫回(Echo)

7.2 初始化ServerSocketChannel

ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 設(shè)置為非阻塞模式
serverChannel.bind(new InetSocketAddress(8888)); // 綁定端口

7.3 創(chuàng)建Selector并注冊(cè)監(jiān)聽

Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 監(jiān)聽連接事件

7.4 主循環(huán)處理事件

while (true) {
    selector.select(); // 阻塞直到有事件就緒
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> iter = selectedKeys.iterator();

    while (iter.hasNext()) {
        SelectionKey key = iter.next();
        iter.remove(); // 清除已處理的key

        if (key.isAcceptable()) {
            handleAccept(key);
        } else if (key.isReadable()) {
            handleRead(key);
        }
    }
}

7.5 接受客戶端連接

private static void handleAccept(SelectionKey key) throws IOException {
    ServerSocketChannel server = (ServerSocketChannel) key.channel();
    SocketChannel clientChannel = server.accept();
    clientChannel.configureBlocking(false);
    clientChannel.register(key.selector(), SelectionKey.OP_READ);
    System.out.println("客戶端連接: " + clientChannel.getRemoteAddress());
}

7.6 讀取并回寫數(shù)據(jù)(Echo功能)

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) {
        clientChannel.close();
        System.out.println("客戶端斷開連接");
        return;
    }

    buffer.flip();
    clientChannel.write(buffer); // Echo 回寫
    buffer.clear();
}

7.7 完整服務(wù)端代碼示例

public class NioEchoServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.bind(new InetSocketAddress(8888));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("NIO服務(wù)器啟動(dòng),端口: 8888");

        while (true) {
            selector.select();
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                iter.remove();

                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                    System.out.println("客戶端連接: " + client.getRemoteAddress());
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int read = client.read(buffer);
                    if (read == -1) {
                        client.close();
                        continue;
                    }
                    buffer.flip();
                    client.write(buffer);
                    buffer.clear();
                }
            }
        }
    }
}

7.8 注意事項(xiàng)

  • 每次Selector輪詢后必須調(diào)用selectedKeys().clear()iterator.remove()清除已處理事件。

  • 客戶端斷開連接時(shí)read()返回-1,應(yīng)關(guān)閉Channel防止資源泄露。

  • ByteBuffer需注意flip()clear()的使用順序。

8. 處理多個(gè)客戶端連接

在構(gòu)建非阻塞NIO服務(wù)器時(shí),處理多個(gè)客戶端連接是最關(guān)鍵的能力之一。本章將繼續(xù)基于第7章的Echo服務(wù)器,擴(kuò)展其功能,使其能夠更高效地管理多個(gè)連接,并實(shí)現(xiàn)更復(fù)雜的業(yè)務(wù)邏輯,如多用戶聊天。

8.1 多客戶端連接的挑戰(zhàn)

雖然Selector已經(jīng)允許我們?cè)谝粋€(gè)線程中監(jiān)聽多個(gè)Channel,但為了支持多個(gè)用戶之間的獨(dú)立通信,我們還需面對(duì)以下挑戰(zhàn):

  • 如何為每個(gè)客戶端維護(hù)狀態(tài)(如昵稱、消息緩存)?

  • 如何管理Channel與客戶端之間的映射?

  • 如何在事件回調(diào)中區(qū)分不同客戶端?

  • 如何避免并發(fā)寫入和粘包、拆包問題?

為此我們需要借助:

  • SelectionKey 的 attach 方法

  • 合理設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)

  • 可能的多線程優(yōu)化(詳見第13章)

8.2 使用 SelectionKey.attach() 綁定客戶端狀態(tài)

Java NIO允許通過SelectionKey.attach(Object obj)綁定任意對(duì)象,從而實(shí)現(xiàn)每個(gè)Channel附帶獨(dú)立上下文數(shù)據(jù)。

示例:綁定客戶端上下文對(duì)象

class ClientContext {
    String username;
    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
    // 可擴(kuò)展更多字段,如身份標(biāo)識(shí)、狀態(tài)等
}

// 注冊(cè)時(shí)綁定
ClientContext context = new ClientContext();
SelectionKey key = clientChannel.register(selector, SelectionKey.OP_READ);
key.attach(context);

8.3 客戶端連接處理優(yōu)化

修改 handleAccept 方法

private static void handleAccept(SelectionKey key) throws IOException {
    ServerSocketChannel server = (ServerSocketChannel) key.channel();
    SocketChannel clientChannel = server.accept();
    clientChannel.configureBlocking(false);

    ClientContext context = new ClientContext();
    SelectionKey clientKey = clientChannel.register(key.selector(), SelectionKey.OP_READ);
    clientKey.attach(context);

    System.out.println("新客戶端接入: " + clientChannel.getRemoteAddress());
}

8.4 可讀事件處理:讀取并打印客戶端信息

private static void handleRead(SelectionKey key) throws IOException {
    SocketChannel channel = (SocketChannel) key.channel();
    ClientContext context = (ClientContext) key.attachment();
    ByteBuffer buffer = context.readBuffer;

    int bytesRead = channel.read(buffer);
    if (bytesRead == -1) {
        System.out.println("客戶端斷開連接: " + channel.getRemoteAddress());
        channel.close();
        return;
    }

    buffer.flip();
    byte[] data = new byte[buffer.remaining()];
    buffer.get(data);
    String message = new String(data);
    System.out.println("收到消息: " + message);

    // 將消息寫入寫緩沖區(qū),準(zhǔn)備寫回(或廣播)
    context.writeBuffer.put(("[Echo] " + message).getBytes());
    buffer.clear();

    // 關(guān)注寫事件
    key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}

8.5 寫事件處理:異步發(fā)送響應(yīng)

private static void handleWrite(SelectionKey key) throws IOException {
    SocketChannel channel = (SocketChannel) key.channel();
    ClientContext context = (ClientContext) key.attachment();
    ByteBuffer buffer = context.writeBuffer;

    buffer.flip();
    channel.write(buffer);
    if (!buffer.hasRemaining()) {
        // 清空后停止關(guān)注寫事件
        key.interestOps(SelectionKey.OP_READ);
        buffer.clear();
    } else {
        buffer.compact(); // 還有數(shù)據(jù)未寫完,下次繼續(xù)
    }
}

8.6 主循環(huán)中處理 WRITE 事件

if (key.isWritable()) {
    handleWrite(key);
}

8.7 小型聊天室服務(wù)器雛形(簡(jiǎn)述)

借助 SelectionKey.attach + 緩沖區(qū)管理 + Channel廣播機(jī)制,我們可以輕松實(shí)現(xiàn)一個(gè)支持多人聊天的服務(wù)器:

  • 所有客戶端注冊(cè)到Selector

  • 每個(gè)客戶端發(fā)言時(shí),將其消息廣播給其他所有客戶端

  • 管理在線客戶端列表,防止死連接

9. 文件IO:FileChannel詳解

Java NIO不僅支持網(wǎng)絡(luò)通信,同樣提供了高效的文件輸入輸出操作。

核心類是 FileChannel,它提供了一種比傳統(tǒng) FileInputStreamFileOutputStream 更現(xiàn)代、更靈活的文件讀寫方式。

9.1 FileChannel簡(jiǎn)介

FileChannel 是一個(gè)連接到文件的通道,常用于:

  • 文件的讀取與寫入

  • 文件內(nèi)容的內(nèi)存映射(MappedByteBuffer)

  • 文件區(qū)域之間的傳輸(transferTo / transferFrom)

  • 多線程共享只讀/讀寫映射

FileChannel 不支持非阻塞模式,它始終是阻塞式的,但相比傳統(tǒng)IO在性能、靈活性上具備明顯優(yōu)勢(shì)。

9.2 打開FileChannel的方式

// 方式1:通過FileInputStream
FileInputStream fis = new FileInputStream("example.txt");
FileChannel readChannel = fis.getChannel();

// 方式2:通過RandomAccessFile
RandomAccessFile raf = new RandomAccessFile("example.txt", "rw");
FileChannel rwChannel = raf.getChannel();

9.3 基本讀寫操作

讀取文件內(nèi)容

FileInputStream fis = new FileInputStream("data.txt");
FileChannel channel = fis.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
    buffer.flip();
    while (buffer.hasRemaining()) {
        System.out.print((char) buffer.get());
    }
    buffer.clear();
    bytesRead = channel.read(buffer);
}
channel.close();

寫入文件內(nèi)容

FileOutputStream fos = new FileOutputStream("output.txt");
FileChannel channel = fos.getChannel();

ByteBuffer buffer = ByteBuffer.wrap("Hello NIO!".getBytes());
channel.write(buffer);
channel.close();

9.4 文件復(fù)制:使用 transferTo 和 transferFrom

這兩個(gè)方法允許在兩個(gè) FileChannel 之間高效傳輸文件內(nèi)容,底層可能使用零拷貝(zero-copy)技術(shù):

FileChannel source = new FileInputStream("source.txt").getChannel();
FileChannel target = new FileOutputStream("target.txt").getChannel();

source.transferTo(0, source.size(), target);
// 或者 target.transferFrom(source, 0, source.size());

source.close();
target.close();

9.5 文件映射:MappedByteBuffer

通過 map() 方法可以將整個(gè)文件或文件的一部分映射到內(nèi)存中,大大提升讀寫性能。

RandomAccessFile raf = new RandomAccessFile("mapped.txt", "rw");
FileChannel channel = raf.getChannel();

MappedByteBuffer mappedBuf = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
mappedBuf.put(0, (byte) 'H'); // 寫入位置0
System.out.println((char) mappedBuf.get(0)); // 讀取位置0

優(yōu)點(diǎn):

  • 避免系統(tǒng)調(diào)用頻繁拷貝

  • 適合大文件處理

注意事項(xiàng):

  • 文件大小不能超過 Integer.MAX_VALUE

  • 映射區(qū)域過大可能導(dǎo)致內(nèi)存不足

9.6 文件鎖(FileLock)

用于防止文件在多個(gè)線程/進(jìn)程中同時(shí)寫入:

FileChannel channel = new RandomAccessFile("lock.txt", "rw").getChannel();
FileLock lock = channel.lock(); // 獨(dú)占鎖(阻塞)

try {
    // 安全寫入
    channel.write(ByteBuffer.wrap("lock write".getBytes()));
} finally {
    lock.release();
    channel.close();
}

可使用 tryLock() 實(shí)現(xiàn)非阻塞鎖嘗試:

FileLock lock = channel.tryLock();

10. 字符集與編碼:Charset與Decoder/Encoder

在進(jìn)行網(wǎng)絡(luò)通信或文件讀寫時(shí),字符的編碼和解碼問題不可忽視,特別是在多語言、國際化、Emoji 表情符號(hào)或特殊字符頻繁出現(xiàn)的場(chǎng)景中。 Java NIO 提供了 CharsetCharsetEncoderCharsetDecoder 等類,用于處理字節(jié)和字符之間的轉(zhuǎn)換,確保數(shù)據(jù)的正確傳輸與顯示。

10.1 字節(jié)與字符的區(qū)別

  • 字節(jié)(Byte):底層數(shù)據(jù)單位,I/O中傳輸?shù)幕驹亍?/p>

  • 字符(Character):表示人類語言的文字,是程序展示給用戶的內(nèi)容。

例如,UTF-8 中一個(gè)漢字可能占 3 個(gè)字節(jié),而一個(gè)英文字符只占 1 個(gè)字節(jié)。

10.2 Charset類概覽

Charset 是 Java 提供的字符集抽象,用于表示編碼方案,如 UTF-8、GBK、ISO-8859-1 等。

常用方法如下:

Charset charset = Charset.forName("UTF-8");

列出所有支持的字符集:

SortedMap<String, Charset> charsets = Charset.availableCharsets();
for (String name : charsets.keySet()) {
    System.out.println(name);
}

10.3 字節(jié)轉(zhuǎn)字符:CharsetDecoder

Charset charset = Charset.forName("UTF-8");
CharsetDecoder decoder = charset.newDecoder();

ByteBuffer byteBuffer = ByteBuffer.wrap("你好,世界".getBytes("UTF-8"));
CharBuffer charBuffer = decoder.decode(byteBuffer);
System.out.println(charBuffer.toString());

10.4 字符轉(zhuǎn)字節(jié):CharsetEncoder

Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();

CharBuffer charBuffer = CharBuffer.wrap("你好,Java NIO");
ByteBuffer byteBuffer = encoder.encode(charBuffer);

while (byteBuffer.hasRemaining()) {
    System.out.print(byteBuffer.get() + " ");
}

10.5 與通道結(jié)合使用示例

將字符串編碼后寫入文件,再從文件中讀取并解碼:

Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
CharsetDecoder decoder = charset.newDecoder();

String text = "Java NIO 字符集測(cè)試";

// 寫入文件
FileChannel outChannel = new FileOutputStream("charset.txt").getChannel();
ByteBuffer buffer = encoder.encode(CharBuffer.wrap(text));
outChannel.write(buffer);
outChannel.close();

// 讀取文件
FileChannel inChannel = new FileInputStream("charset.txt").getChannel();
ByteBuffer inBuffer = ByteBuffer.allocate(1024);
inChannel.read(inBuffer);
inBuffer.flip();
CharBuffer result = decoder.decode(inBuffer);
System.out.println(result.toString());
inChannel.close();

10.6 常見編碼問題

中文亂碼

通常由于編碼解碼不一致,例如寫入時(shí)用 UTF-8,讀取時(shí)用 ISO-8859-1。

字節(jié)緩沖不足

編碼大段文本時(shí)需要確保 ByteBuffer 和 CharBuffer 足夠大。

不兼容字符

某些字符(如 Emoji)在 GBK 中無法表示,編碼時(shí)可能拋異常,可配置處理策略:

decoder.onMalformedInput(CodingErrorAction.REPLACE);
decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);

11. 散布與集聚(Scattering & Gathering)

在傳統(tǒng) I/O 模型中,一次讀/寫操作通常只能針對(duì)一個(gè)緩沖區(qū)進(jìn)行處理。 而 Java NIO 提供的 Scattering ReadsGathering Writes 功能,使得我們可以在一次通道讀寫操作中同時(shí)處理多個(gè)緩沖區(qū),大幅提升數(shù)據(jù)結(jié)構(gòu)清晰性與靈活性,特別適合協(xié)議頭-體分離等應(yīng)用場(chǎng)景。

11.1 什么是散布讀?。⊿cattering Read)?

散布讀取是指從 Channel 中讀取的數(shù)據(jù)依次填充到多個(gè) ByteBuffer 中,就像把一段數(shù)據(jù)"撒開"一樣。 適用于:

  • 網(wǎng)絡(luò)通信中讀取固定頭部 + 可變數(shù)據(jù)體

  • 文件格式中按照結(jié)構(gòu)字段分區(qū)

示例:讀取頭部和正文

RandomAccessFile raf = new RandomAccessFile("scatter.txt", "rw");
FileChannel channel = raf.getChannel();

ByteBuffer header = ByteBuffer.allocate(8);  // 假設(shè)頭部8字節(jié)
ByteBuffer body = ByteBuffer.allocate(32);   // 正文最大32字節(jié)

ByteBuffer[] buffers = {header, body};
channel.read(buffers); // 按順序填滿 header,再填 body

header.flip();
body.flip();

System.out.println("Header:");
while (header.hasRemaining()) {
    System.out.print((char) header.get());
}
System.out.println("\nBody:");
while (body.hasRemaining()) {
    System.out.print((char) body.get());
}
channel.close();

11.2 什么是集聚寫入(Gathering Write)?

集聚寫入是指將多個(gè)緩沖區(qū)中的內(nèi)容依次寫入同一個(gè) Channel,就像把數(shù)據(jù)"聚合"起來寫出。

適用于:

  • 構(gòu)造多個(gè)片段組成的報(bào)文

  • 高效構(gòu)造文件結(jié)構(gòu)、日志輸出等

示例:拼接寫入頭部和正文

RandomAccessFile raf = new RandomAccessFile("gather.txt", "rw");
FileChannel channel = raf.getChannel();

ByteBuffer header = ByteBuffer.wrap("HEAD1234".getBytes());
ByteBuffer body = ByteBuffer.wrap("This is the body content.".getBytes());

ByteBuffer[] buffers = {header, body};
channel.write(buffers); // 會(huì)依次寫出 header 和 body
channel.close();

11.3 使用限制和注意事項(xiàng)

  • 所有緩沖區(qū)必須是 寫模式(read 模式會(huì)導(dǎo)致0寫入),即 position <= limit

  • 實(shí)際讀取/寫入的總字節(jié)數(shù)由 Channel 決定,可能小于總緩沖容量

  • 如果緩沖區(qū)數(shù)組較大,應(yīng)控制單次 read/write 的緩沖個(gè)數(shù),避免內(nèi)存壓力

  • 順序重要:Channel 會(huì)按數(shù)組順序處理每個(gè)緩沖區(qū)

11.4 應(yīng)用場(chǎng)景

網(wǎng)絡(luò)協(xié)議處理

TCP報(bào)文結(jié)構(gòu)如:

+---------+--------------+
| Header  |   Payload    |
| (固定)  |   (可變)     |
+---------+--------------+

讀取時(shí)就可以使用:

ByteBuffer header = ByteBuffer.allocate(12);
ByteBuffer payload = ByteBuffer.allocate(1024);
channel.read(new ByteBuffer[]{header, payload});

構(gòu)造響應(yīng)數(shù)據(jù)包

ByteBuffer httpHeader = ByteBuffer.wrap("HTTP/1.1 200 OK\r\n\r\n".getBytes());
ByteBuffer content = ByteBuffer.wrap("Hello, client!".getBytes());
socketChannel.write(new ByteBuffer[]{httpHeader, content});

12. AsynchronousChannelGroup 與 AIO(異步IO)

Java 7 引入了 Asynchronous I/O(異步IO)支持,旨在進(jìn)一步提升Java程序在高并發(fā)、高性能網(wǎng)絡(luò)和文件I/O場(chǎng)景下的處理能力。

相比傳統(tǒng)NIO的同步非阻塞模式(Selector機(jī)制),AIO通過操作系統(tǒng)底層的異步機(jī)制和回調(diào)設(shè)計(jì),避免了線程阻塞,實(shí)現(xiàn)真正的異步操作。

12.1 異步通道簡(jiǎn)介

Java的異步通道主要位于 java.nio.channels 包,包含如下核心類:

  • AsynchronousSocketChannel:用于異步TCP客戶端和服務(wù)器端的Socket通信。

  • AsynchronousServerSocketChannel:異步服務(wù)器套接字,用于接收客戶端連接。

  • AsynchronousFileChannel:異步文件讀寫通道。

這些通道的操作是非阻塞的,所有的讀寫操作通過回調(diào)(CompletionHandler)或 Future 接口進(jìn)行異步處理。

12.2 AsynchronousChannelGroup

AsynchronousChannelGroup 是異步通道的線程資源管理器,管理一組通道共享的線程池。它幫助我們合理調(diào)度和限制線程數(shù),避免資源浪費(fèi)。

  • 創(chuàng)建方式:

ExecutorService threadPool = Executors.newFixedThreadPool(4);
AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(threadPool);
  • 使用 AsynchronousChannelGroup,多個(gè)通道可以共享一個(gè)線程池,提升資源復(fù)用率。

12.3 異步服務(wù)器示例

下面演示一個(gè)簡(jiǎn)單的異步TCP服務(wù)器,使用 AsynchronousServerSocketChannel 接收客戶端連接并異步讀取數(shù)據(jù):

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class AsyncNIOServer {

    public static void main(String[] args) throws Exception {
        ExecutorService threadPool = Executors.newFixedThreadPool(4);
        AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(threadPool);

        AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group)
            .bind(new InetSocketAddress(8080));

        System.out.println("服務(wù)器已啟動(dòng),等待客戶端連接...");

        server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Void attachment) {
                // 繼續(xù)接收其他客戶端連接
                server.accept(null, this);

                ByteBuffer buffer = ByteBuffer.allocate(1024);

                // 異步讀取客戶端數(shù)據(jù)
                client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer bytesRead, ByteBuffer buf) {
                        if (bytesRead == -1) {
                            try {
                                client.close();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return;
                        }

                        buf.flip();
                        byte[] data = new byte[buf.remaining()];
                        buf.get(data);
                        System.out.println("收到客戶端消息: " + new String(data));

                        // 異步寫回客戶端
                        client.write(ByteBuffer.wrap("收到消息,謝謝!".getBytes()), null, new CompletionHandler<Integer, Void>() {
                            @Override
                            public void completed(Integer result, Void attachment) {
                                // 寫入完成后繼續(xù)讀取
                                buf.clear();
                                client.read(buf, buf, this);
                            }

                            @Override
                            public void failed(Throwable exc, Void attachment) {
                                exc.printStackTrace();
                                try { client.close(); } catch (Exception e) { e.printStackTrace(); }
                            }
                        });
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer buf) {
                        exc.printStackTrace();
                        try { client.close(); } catch (Exception e) { e.printStackTrace(); }
                    }
                });
            }

            @Override
            public void failed(Throwable exc, Void attachment) {
                exc.printStackTrace();
            }
        });

        // 主線程可以繼續(xù)執(zhí)行其他任務(wù)
        Thread.currentThread().join();
    }
}

該示例重點(diǎn):

  • 服務(wù)器啟動(dòng)后調(diào)用 accept(),并傳入回調(diào) CompletionHandler

  • 每當(dāng)有客戶端連接時(shí),先再次調(diào)用 accept() 繼續(xù)監(jiān)聽其他連接。

  • 使用 read() 異步讀取數(shù)據(jù),讀取完成后在回調(diào)中處理數(shù)據(jù)并異步寫回。

  • 整個(gè)過程完全異步,不阻塞主線程。

12.4 異步文件操作示例

AsynchronousFileChannel 支持異步讀寫文件,適合處理大文件或高并發(fā)文件I/O。

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class AsyncFileReadExample {

    public static void main(String[] args) throws Exception {
        AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
                Paths.get("asyncFile.txt"), StandardOpenOption.READ);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        fileChannel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer bytesRead, ByteBuffer buf) {
                System.out.println("讀取到字節(jié)數(shù): " + bytesRead);
                buf.flip();
                while (buf.hasRemaining()) {
                    System.out.print((char) buf.get());
                }
                try {
                    fileChannel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer buf) {
                System.err.println("讀取失敗: " + exc);
                try {
                    fileChannel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        // 主線程可以繼續(xù)執(zhí)行其他邏輯
        Thread.sleep(3000);
    }
}

該示例演示了文件異步讀取,通過回調(diào)獲取讀取結(jié)果并處理。

12.5 性能與應(yīng)用場(chǎng)景

  • 適用場(chǎng)景:適合高并發(fā)、高吞吐的網(wǎng)絡(luò)服務(wù)器和文件服務(wù)器,能夠減少線程阻塞和上下文切換。

優(yōu)勢(shì)

  • 真正異步,系統(tǒng)調(diào)用效率高。

  • 利用線程池共享資源,提高系統(tǒng)負(fù)載能力。

缺點(diǎn)

  • 編程復(fù)雜度提升,調(diào)試和異常處理更繁瑣。

  • 不同平臺(tái)支持程度略有差異,依賴底層操作系統(tǒng)。

13. 性能考慮:何時(shí)使用NIO及潛在瓶頸

Java NIO 提供了基于緩沖區(qū)、通道和選擇器的高效IO模型,尤其適合構(gòu)建高并發(fā)網(wǎng)絡(luò)應(yīng)用和高性能文件處理系統(tǒng)。但并不是所有場(chǎng)景都適合NIO,選擇合理的IO模型對(duì)于系統(tǒng)性能和穩(wěn)定性至關(guān)重要。本章將從性能角度深入分析NIO的優(yōu)勢(shì)、潛在瓶頸,并給出實(shí)際使用建議。

13.1 NIO的性能優(yōu)勢(shì)

  • 減少線程阻塞
    NIO通過非阻塞IO和Selector,允許單個(gè)線程同時(shí)管理多個(gè)通道,大大減少了線程數(shù)量和上下文切換開銷。

  • 零拷貝技術(shù)
    通過FileChannel.transferTo/transferFrom和內(nèi)存映射文件,減少用戶空間與內(nèi)核空間數(shù)據(jù)拷貝,提高傳輸效率。

  • 緩沖區(qū)管理
    直接緩沖區(qū)(DirectByteBuffer)將數(shù)據(jù)緩存在本地內(nèi)存,減少GC壓力及數(shù)據(jù)復(fù)制,提升性能。

  • 事件驅(qū)動(dòng)模型
    Selector提供多路復(fù)用機(jī)制,避免輪詢和阻塞等待,提高資源利用率。

13.2 NIO潛在瓶頸與限制

  • 選擇器的伸縮性問題
    傳統(tǒng)Selector在連接數(shù)量極大(數(shù)萬級(jí)別)時(shí),性能會(huì)下降,出現(xiàn)“驚群效應(yīng)”和事件輪詢延遲。

  • 直接緩沖區(qū)的開銷
    雖然性能較好,但分配和回收成本高,頻繁創(chuàng)建大量直接緩沖區(qū)可能導(dǎo)致內(nèi)存碎片和系統(tǒng)壓力。

  • 復(fù)雜的異步編程模型
    NIO編程復(fù)雜,容易出錯(cuò)。錯(cuò)誤處理不當(dāng)可能導(dǎo)致資源泄漏、死鎖或性能下降。

  • 平臺(tái)差異性
    不同操作系統(tǒng)對(duì)NIO底層實(shí)現(xiàn)不同,表現(xiàn)差異較大。Linux下基于epoll,Windows基于IOCP。

13.3 何時(shí)使用NIO

場(chǎng)景類型建議IO模型理由
小連接數(shù)、低并發(fā)阻塞IO代碼簡(jiǎn)單,性能開銷較小,易維護(hù)
高并發(fā)連接、輕量數(shù)據(jù)傳輸NIO(非阻塞IO)減少線程數(shù)量,提高并發(fā)處理能力
大文件讀寫NIO FileChannel零拷貝,內(nèi)存映射,提升文件操作效率
極致性能需求AIO(異步IO)最大化線程資源利用,適合高吞吐量和低延遲的系統(tǒng)

13.4 性能優(yōu)化建議

  • 合理使用直接緩沖區(qū)
    對(duì)于頻繁IO操作,建議預(yù)先分配直接緩沖區(qū)并重用,避免頻繁分配和GC壓力。

  • 選擇合適的Selector線程數(shù)
    多核服務(wù)器可使用多個(gè)Selector實(shí)例分擔(dān)連接負(fù)載,避免單Selector瓶頸。

  • 避免阻塞操作
    在線程池任務(wù)中避免長時(shí)間阻塞,防止線程饑餓。

  • 結(jié)合業(yè)務(wù)協(xié)議優(yōu)化讀寫邏輯
    根據(jù)協(xié)議報(bào)文特點(diǎn)設(shè)計(jì)緩沖區(qū)讀寫策略,減少不必要的系統(tǒng)調(diào)用。

  • 監(jiān)控和日志
    及時(shí)監(jiān)控Selector的select事件數(shù)量、線程狀態(tài)、緩沖區(qū)使用,快速定位性能瓶頸。

13.5 典型性能瓶頸案例分析

13.5.1 連接數(shù)激增導(dǎo)致Selector性能下降

在高并發(fā)場(chǎng)景下,單Selector管理過多連接,select調(diào)用延遲增加。解決方案:

  • 采用多Selector多線程模型,將連接分散管理。

  • 結(jié)合AIO實(shí)現(xiàn)異步分發(fā)。

13.5.2 頻繁分配直接緩沖區(qū)導(dǎo)致內(nèi)存碎片

應(yīng)用中未重用緩沖區(qū),導(dǎo)致頻繁GC和內(nèi)存碎片。建議:

  • 使用緩沖區(qū)池化技術(shù)。

  • 預(yù)分配緩沖區(qū),循環(huán)使用。

14. 常見陷阱與解決方法

在Java NIO的使用過程中,盡管它提供了強(qiáng)大的非阻塞和高性能能力,但開發(fā)者也常常會(huì)遇到一些典型問題和陷阱。掌握這些坑的成因及對(duì)應(yīng)解決方案,對(duì)穩(wěn)定高效地構(gòu)建NIO應(yīng)用至關(guān)重要。

14.1 選擇器空輪詢(Selector Busy Loop)

問題描述

Selector調(diào)用 select() 時(shí),無任何通道發(fā)生事件,但CPU使用率異常升高,程序進(jìn)入空輪詢狀態(tài)。

產(chǎn)生原因

  • 有時(shí)底層Selector內(nèi)部狀態(tài)錯(cuò)亂,導(dǎo)致select()方法立即返回零。

  • 造成CPU被占滿,導(dǎo)致程序性能嚴(yán)重下降。

解決方案

  • 在循環(huán)調(diào)用select()時(shí),若發(fā)現(xiàn)返回值為0,添加適當(dāng)短暫休眠(如10ms)來避免忙循環(huán)。

  • 或者重建Selector實(shí)例,替換失效的Selector。

示例代碼:

while (true) {
    int readyChannels = selector.select();
    if (readyChannels == 0) {
        Thread.sleep(10); // 避免空輪詢CPU飆升
        continue;
    }
    // 處理IO事件...
}

14.2 讀寫緩沖區(qū)切換錯(cuò)誤

問題描述

開發(fā)中常見的緩沖區(qū)狀態(tài)管理不當(dāng),導(dǎo)致數(shù)據(jù)讀取或?qū)懭氘惓#鐢?shù)據(jù)丟失或亂序。

產(chǎn)生原因

  • ByteBufferflip()、clear()、rewind()方法未正確使用,緩沖區(qū)讀寫指針混亂。

  • 例如讀寫時(shí)未調(diào)用flip(),導(dǎo)致緩沖區(qū)position錯(cuò)誤。

解決方案

  • 寫入后調(diào)用flip()切換到讀模式。

  • 讀完后調(diào)用clear()準(zhǔn)備寫入新數(shù)據(jù)。

  • 牢記緩沖區(qū)讀寫模式轉(zhuǎn)換規(guī)范。

14.3 連接泄漏

問題描述

服務(wù)器長時(shí)間運(yùn)行后,連接資源不釋放,導(dǎo)致文件描述符耗盡。

產(chǎn)生原因

  • 未正確關(guān)閉SocketChannelAsynchronousSocketChannel。

  • 異常情況下未釋放通道,導(dǎo)致資源泄漏。

解決方案

  • 使用try-with-resources或finally塊確保通道關(guān)閉。

  • 捕獲異常時(shí)也應(yīng)關(guān)閉連接。

  • 使用連接池時(shí)嚴(yán)格管理連接生命周期。

14.4 多線程并發(fā)操作Selector

問題描述

多線程同時(shí)操作同一個(gè)Selector,拋出ConcurrentModificationException或?qū)е鲁绦虿环€(wěn)定。

產(chǎn)生原因

  • Selector不是線程安全的,不允許多線程并發(fā)調(diào)用select()wakeup()等方法。

解決方案

  • 統(tǒng)一由單個(gè)線程負(fù)責(zé)Selector的select()調(diào)用。

  • 其他線程通過selector.wakeup()方法喚醒Selector線程。

  • 共享的事件注冊(cè)操作使用線程安全隊(duì)列,由Selector線程完成注冊(cè)。

14.5 事件處理遺漏

問題描述

Selector事件就緒后,沒有正確處理所有事件,導(dǎo)致連接阻塞或死鎖。

產(chǎn)生原因

  • 只處理了部分SelectionKey事件,忽略了讀寫狀態(tài)切換。

  • 沒有正確調(diào)用key.interestOps()更新關(guān)注事件。

解決方案

  • 逐個(gè)處理所有就緒的SelectionKey。

  • 根據(jù)處理結(jié)果動(dòng)態(tài)更新興趣集(interestOps),避免重復(fù)觸發(fā)無效事件。

  • 確保讀寫操作及時(shí)完成,不阻塞。

14.6 處理大數(shù)據(jù)時(shí)內(nèi)存不足

問題描述

大文件或長連接傳輸大數(shù)據(jù)時(shí),因緩沖區(qū)不合理導(dǎo)致頻繁擴(kuò)容或OOM。

產(chǎn)生原因

  • 緩沖區(qū)預(yù)分配過小,頻繁分配擴(kuò)容。

  • 未限制讀寫速度,導(dǎo)致內(nèi)存使用激增。

解決方案

  • 設(shè)計(jì)合理緩沖區(qū)大小,結(jié)合業(yè)務(wù)協(xié)議特征。

  • 使用直接緩沖區(qū)減少堆內(nèi)存壓力。

  • 對(duì)讀寫數(shù)據(jù)做限流或分片處理。

14.7 非阻塞IO下的寫操作未完成處理

問題描述

非阻塞IO寫入時(shí),可能一次寫入不完整,導(dǎo)致數(shù)據(jù)丟失。

產(chǎn)生原因

  • 寫操作未檢查返回的寫入字節(jié)數(shù),未保存剩余數(shù)據(jù)。

解決方案

  • 保存未寫完的緩沖區(qū),注冊(cè)寫事件,等待下一次寫操作繼續(xù)寫入。

  • 只有確認(rèn)數(shù)據(jù)全部寫完后,才取消寫事件監(jiān)聽。

14.8 總結(jié)

常見陷阱根因解決方案
Selector空輪詢Selector狀態(tài)異常適當(dāng)休眠,重建Selector
緩沖區(qū)切換錯(cuò)誤緩沖區(qū)讀寫指針管理不當(dāng)正確使用flip/clear切換模式
連接資源泄漏通道未關(guān)閉異常處理及時(shí)關(guān)閉通道
多線程操作SelectorSelector非線程安全單線程操作Selector,使用wakeup喚醒
事件處理遺漏未處理所有就緒事件遍歷全部SelectionKey,更新興趣集
大數(shù)據(jù)內(nèi)存壓力緩沖區(qū)設(shè)計(jì)不合理合理預(yù)分配緩沖區(qū),限流分片
非阻塞寫未完成處理未保存寫緩沖區(qū)剩余數(shù)據(jù)保存未寫完數(shù)據(jù),繼續(xù)寫直到完成

熟練避免和解決上述問題,將顯著提升Java NIO項(xiàng)目的穩(wěn)定性和性能。

15. 總結(jié)與展望

15.1 本文核心內(nèi)容回顧

本文從Java IO多路復(fù)用的基礎(chǔ)概念入手,系統(tǒng)、全面地介紹了Java NIO的關(guān)鍵技術(shù)點(diǎn),包括:

  • IO多路復(fù)用的基本原理與重要性:理解了阻塞IO與非阻塞IO的區(qū)別,認(rèn)識(shí)到多路復(fù)用是提升高并發(fā)網(wǎng)絡(luò)應(yīng)用性能的關(guān)鍵技術(shù)。

  • Java中的IO模型:詳細(xì)對(duì)比了傳統(tǒng)阻塞IO和NIO非阻塞IO的實(shí)現(xiàn)機(jī)制及應(yīng)用場(chǎng)景。

  • Java NIO核心組件:深入剖析了Channels、Buffers、Selectors等核心類的工作原理和使用方法。

  • 網(wǎng)絡(luò)編程示例:通過實(shí)際代碼示例,演示了如何使用Selector實(shí)現(xiàn)一個(gè)高效的多客戶端服務(wù)器。

  • 文件IO與高級(jí)功能:重點(diǎn)介紹了FileChannel的應(yīng)用、零拷貝技術(shù)、內(nèi)存映射文件以及文件鎖機(jī)制。

  • 字符集和編碼:講解了字符編碼的重要性和Java中的編碼轉(zhuǎn)換機(jī)制,確保數(shù)據(jù)的正確傳輸與存儲(chǔ)。

  • 散布/聚集操作:展示了NIO中如何通過Scatter/Gather操作高效處理復(fù)合數(shù)據(jù)結(jié)構(gòu)。

  • 異步IO (AIO):介紹了Java異步通道組及其應(yīng)用,滿足極致性能需求的場(chǎng)景。

  • 性能分析與優(yōu)化建議:總結(jié)了NIO在性能上的優(yōu)勢(shì)與潛在瓶頸,給出切實(shí)可行的優(yōu)化策略。

  • 常見坑與解決方案:列舉了NIO開發(fā)中遇到的典型問題及其應(yīng)對(duì)措施,幫助讀者避免陷阱。

15.2 NIO的優(yōu)勢(shì)與挑戰(zhàn)

Java NIO極大地提升了Java在網(wǎng)絡(luò)和文件IO上的性能和擴(kuò)展性,尤其適用于:

  • 高并發(fā)連接的網(wǎng)絡(luò)服務(wù)器

  • 大規(guī)模文件處理與傳輸

  • 低延遲、事件驅(qū)動(dòng)的異步應(yīng)用

但同時(shí),NIO的學(xué)習(xí)曲線較陡,API使用復(fù)雜,容易出現(xiàn)資源泄漏和狀態(tài)管理錯(cuò)誤。開發(fā)者需要對(duì)其底層機(jī)制有深入理解,并進(jìn)行充分測(cè)試和性能調(diào)優(yōu)。

15.3 未來展望

隨著Java版本不斷更新,NIO相關(guān)技術(shù)也在持續(xù)發(fā)展:

  • 更完善的異步IO支持:Java 7引入了AIO,后續(xù)版本不斷優(yōu)化異步通道,提升易用性和性能。

  • 增強(qiáng)的多路復(fù)用技術(shù):針對(duì)大規(guī)模連接的“驚群效應(yīng)”等問題,業(yè)界持續(xù)探索更高效的事件通知機(jī)制。

  • 與現(xiàn)代網(wǎng)絡(luò)框架結(jié)合:如Netty、Vert.x等基于NIO封裝的高性能框架,為開發(fā)者提供了更簡(jiǎn)潔和健壯的接口。

  • 云原生和微服務(wù)架構(gòu)需求:在云計(jì)算環(huán)境中,NIO技術(shù)的非阻塞和高效特性尤為重要。

15.4 建議與學(xué)習(xí)路徑

  • 理解基礎(chǔ)概念:先掌握阻塞IO和非阻塞IO的區(qū)別,理解多路復(fù)用的原理。

  • 動(dòng)手實(shí)踐:通過實(shí)現(xiàn)簡(jiǎn)單的NIO服務(wù)器和客戶端,加深對(duì)Channels、Buffers和Selectors的理解。

  • 閱讀源碼與框架:深入研究Java官方NIO源碼及主流網(wǎng)絡(luò)框架源碼,提升設(shè)計(jì)能力。

  • 關(guān)注社區(qū)與新特性:跟蹤JDK更新,學(xué)習(xí)最新異步和多路復(fù)用技術(shù)。

Java IO多路復(fù)用是構(gòu)建高性能網(wǎng)絡(luò)和文件IO系統(tǒng)的基石。掌握NIO技術(shù),不僅能提升系統(tǒng)吞吐量和響應(yīng)速度,還能為未來架構(gòu)設(shè)計(jì)提供強(qiáng)有力的支持。

希望本文能幫助你系統(tǒng)理解Java NIO,掌握實(shí)戰(zhàn)技能,順利打造高效穩(wěn)定的應(yīng)用系統(tǒng)。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringCloud及Nacos服務(wù)注冊(cè)IP選擇問題解決方法

    SpringCloud及Nacos服務(wù)注冊(cè)IP選擇問題解決方法

    這篇文章主要介紹了SpringCloud及Nacos服務(wù)注冊(cè)IP選擇問題,為什么注冊(cè)的IP和真實(shí)IP不符合呢,原因是Nacos客戶端在注冊(cè)服務(wù)時(shí)會(huì)從機(jī)器網(wǎng)卡中選擇一個(gè)IP來注冊(cè),所以,當(dāng)注冊(cè)了的是非真實(shí)IP后,另一臺(tái)機(jī)器調(diào)用時(shí)是不可能調(diào)通的,知道問題原因就是解決方法,一起看看吧
    2024-01-01
  • Java中定時(shí)任務(wù)的全方位場(chǎng)景實(shí)現(xiàn)思路分析

    Java中定時(shí)任務(wù)的全方位場(chǎng)景實(shí)現(xiàn)思路分析

    在開發(fā)過程中,根據(jù)需求和業(yè)務(wù)的不同經(jīng)常會(huì)有很多場(chǎng)景需要用到不同特性的定時(shí)任務(wù),本文將針對(duì)這些場(chǎng)景,提供不同的一個(gè)實(shí)現(xiàn)思路,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧
    2023-12-12
  • Java MCP 鑒權(quán)設(shè)計(jì)與實(shí)現(xiàn)指南(完整示例)

    Java MCP 鑒權(quán)設(shè)計(jì)與實(shí)現(xiàn)指南(完整示例)

    MCP鑒權(quán)為大語言模型集成提供安全機(jī)制,涵蓋服務(wù)端過濾器、注解及客戶端Basic、Header、QueryString等方式,適配不同通信通道與框架,需遵循最佳實(shí)踐保障安全性,本文給大家介紹Java MCP 鑒權(quán)設(shè)計(jì)與實(shí)現(xiàn)指南,感興趣的朋友一起看看吧
    2025-07-07
  • Java反轉(zhuǎn)數(shù)組輸出實(shí)例代碼

    Java反轉(zhuǎn)數(shù)組輸出實(shí)例代碼

    這篇文章主要給大家介紹了關(guān)于Java反轉(zhuǎn)數(shù)組輸出以及利用Java實(shí)現(xiàn)字符串逆序輸出的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • mybatis TypeHandler注入spring的依賴方式

    mybatis TypeHandler注入spring的依賴方式

    這篇文章主要介紹了mybatis TypeHandler注入spring的依賴方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • 淺談Spring IoC容器的依賴注入原理

    淺談Spring IoC容器的依賴注入原理

    這篇文章主要介紹了淺談Spring IoC容器的依賴注入原理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03
  • IDEA下SpringBoot指定配置文件啟動(dòng)項(xiàng)目的全過程

    IDEA下SpringBoot指定配置文件啟動(dòng)項(xiàng)目的全過程

    我們?cè)谑褂胹pringboot項(xiàng)目開發(fā)的時(shí)候,每次切換環(huán)境跑項(xiàng)目的時(shí)候,都得修改配置文件的數(shù)據(jù)庫地址,這樣來回修改感覺很麻煩,這篇文章主要給大家介紹了關(guān)于IDEA下SpringBoot指定配置文件啟動(dòng)項(xiàng)目的相關(guān)資料,需要的朋友可以參考下
    2023-06-06
  • Java中Exception和Error的區(qū)別詳解

    Java中Exception和Error的區(qū)別詳解

    這篇文章主要介紹了Java中Exception和Error的區(qū)別詳解,通過類的關(guān)系分析兩者的區(qū)別與應(yīng)用場(chǎng)景,包含代碼實(shí)例,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • SpringBoot動(dòng)態(tài)Feign服務(wù)調(diào)用詳解

    SpringBoot動(dòng)態(tài)Feign服務(wù)調(diào)用詳解

    Feign是Netflix公司開發(fā)的一個(gè)聲明式的REST調(diào)用客戶端; Ribbon負(fù)載均衡、 Hystrⅸ服務(wù)熔斷是我們Spring Cloud中進(jìn)行微服務(wù)開發(fā)非常基礎(chǔ)的組件,在使用的過程中我們也發(fā)現(xiàn)它們一般都是同時(shí)出現(xiàn)的,而且配置也都非常相似
    2022-12-12
  • Java JSONObject與JSONArray對(duì)象案例詳解

    Java JSONObject與JSONArray對(duì)象案例詳解

    這篇文章主要介紹了Java JSONObject與JSONArray對(duì)象案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-09-09

最新評(píng)論

房产| 托克托县| 新民市| 毕节市| 湘阴县| 贵港市| 海安县| 千阳县| 钦州市| 临澧县| 遂平县| 嘉禾县| 平果县| 屏东市| 石家庄市| 特克斯县| 蒙山县| 宁城县| 高唐县| 青铜峡市| 西丰县| 内江市| 元朗区| 石屏县| 云梦县| 赫章县| 晋宁县| 寿光市| 宁河县| 林州市| 阿荣旗| 马关县| 明溪县| 邢台县| 泾川县| 闽清县| 读书| 开封市| 温州市| 汉寿县| 庆城县|