一文理解:Java NIO 核心組件

背景知識(shí)

同步冰垄、異步、阻塞捻勉、非阻塞

首先镀梭,這幾個(gè)概念非常容易搞混淆,但NIO中又有涉及踱启,所以總結(jié)一下报账。

  • 同步:API調(diào)用返回時(shí)調(diào)用者就知道操作的結(jié)果如何了(實(shí)際讀取/寫(xiě)入了多少字節(jié))。
  • 異步:相對(duì)于同步埠偿,API調(diào)用返回時(shí)調(diào)用者不知道操作的結(jié)果透罢,后面才會(huì)回調(diào)通知結(jié)果。
  • 阻塞:當(dāng)無(wú)數(shù)據(jù)可讀冠蒋,或者不能寫(xiě)入所有數(shù)據(jù)時(shí)羽圃,掛起當(dāng)前線程等待。
  • 非阻塞:讀取時(shí)抖剿,可以讀多少數(shù)據(jù)就讀多少然后返回朽寞,寫(xiě)入時(shí),可以寫(xiě)入多少數(shù)據(jù)就寫(xiě)入多少然后返回斩郎。

對(duì)于I/O操作愁憔,根據(jù)Oracle官網(wǎng)的文檔,同步異步的劃分標(biāo)準(zhǔn)是“調(diào)用者是否需要等待I/O操作完成”孽拷,這個(gè)“等待I/O操作完成”的意思不是指一定要讀取到數(shù)據(jù)或者說(shuō)寫(xiě)入所有數(shù)據(jù)吨掌,而是指真正進(jìn)行I/O操作時(shí),比如數(shù)據(jù)在TCP/IP協(xié)議棧緩沖區(qū)和JVM緩沖區(qū)之間傳輸?shù)倪@段時(shí)間脓恕,調(diào)用者是否要等待膜宋。

所以,我們常用的 read() 和 write() 方法都是同步I/O炼幔,同步I/O又分為阻塞和非阻塞兩種模式秋茫,如果是非阻塞模式,檢測(cè)到無(wú)數(shù)據(jù)可讀時(shí)乃秀,直接就返回了肛著,并沒(méi)有真正執(zhí)行I/O操作。

總結(jié)就是跺讯,Java中實(shí)際上只有 同步阻塞I/O枢贿、同步非阻塞I/O 與 異步I/O 三種機(jī)制,我們下文所說(shuō)的是前兩種刀脏,JDK 1.7才開(kāi)始引入異步 I/O局荚,那稱之為NIO.2。

傳統(tǒng)IO

我們知道,一個(gè)新技術(shù)的出現(xiàn)總是伴隨著改進(jìn)和提升耀态,Java NIO的出現(xiàn)亦如此轮傍。

傳統(tǒng) I/O 是阻塞式I/O,主要問(wèn)題是系統(tǒng)資源的浪費(fèi)首装。比如我們?yōu)榱俗x取一個(gè)TCP連接的數(shù)據(jù)创夜,調(diào)用 InputStream 的 read() 方法,這會(huì)使當(dāng)前線程被掛起仙逻,直到有數(shù)據(jù)到達(dá)才被喚醒挥下,那該線程在數(shù)據(jù)到達(dá)這段時(shí)間內(nèi),占用著內(nèi)存資源(存儲(chǔ)線程棧)卻無(wú)所作為桨醋,也就是俗話說(shuō)的占著茅坑不拉屎棚瘟,為了讀取其他連接的數(shù)據(jù),我們不得不啟動(dòng)另外的線程喜最。在并發(fā)連接數(shù)量不多的時(shí)候偎蘸,這可能沒(méi)什么問(wèn)題,然而當(dāng)連接數(shù)量達(dá)到一定規(guī)模瞬内,內(nèi)存資源會(huì)被大量線程消耗殆盡迷雪。另一方面,線程切換需要更改處理器的狀態(tài)虫蝶,比如程序計(jì)數(shù)器章咧、寄存器的值,因此非常頻繁的在大量線程之間切換能真,同樣是一種資源浪費(fèi)赁严。

隨著技術(shù)的發(fā)展,現(xiàn)代操作系統(tǒng)提供了新的I/O機(jī)制粉铐,可以避免這種資源浪費(fèi)疼约。基于此蝙泼,誕生了Java NIO程剥,NIO的代表性特征就是非阻塞I/O。緊接著我們發(fā)現(xiàn)汤踏,簡(jiǎn)單的使用非阻塞I/O并不能解決問(wèn)題织鲸,因?yàn)樵诜亲枞J较拢瑀ead()方法在沒(méi)有讀取到數(shù)據(jù)時(shí)就會(huì)立即返回溪胶,不知道數(shù)據(jù)何時(shí)到達(dá)的我們搂擦,只能不停的調(diào)用read()方法進(jìn)行重試,這顯然太浪費(fèi)CPU資源了载荔,從下文可以知道盾饮,Selector組件正是為解決此問(wèn)題而生采桃。

Java NIO 核心組件

1.Channel

概念

Java NIO中的所有I/O操作都基于Channel對(duì)象懒熙,就像流操作都要基于Stream對(duì)象一樣丘损,因此很有必要先了解Channel是什么。以下內(nèi)容摘自JDK 1.8的文檔

A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.

從上述內(nèi)容可知工扎,一個(gè)Channel(通道)代表和某一實(shí)體的連接徘钥,這個(gè)實(shí)體可以是文件、網(wǎng)絡(luò)套接字等肢娘。也就是說(shuō)呈础,通道是Java NIO提供的一座橋梁,用于我們的程序和操作系統(tǒng)底層I/O服務(wù)進(jìn)行交互橱健。

通道是一種很基本很抽象的描述而钞,和不同的I/O服務(wù)交互,執(zhí)行不同的I/O操作拘荡,實(shí)現(xiàn)不一樣臼节,因此具體的有FileChannel、SocketChannel等珊皿。加群895244712网缝,免費(fèi)獲取Java架構(gòu)師進(jìn)階學(xué)習(xí)資料

通道使用起來(lái)跟Stream比較像,可以讀取數(shù)據(jù)到Buffer中蟋定,也可以把Buffer中的數(shù)據(jù)寫(xiě)入通道粉臊。

當(dāng)然,也有區(qū)別驶兜,主要體現(xiàn)在如下兩點(diǎn):

  • 一個(gè)通道扼仲,既可以讀又可以寫(xiě),而一個(gè)Stream是單向的(所以分 InputStream 和 OutputStream)
  • 通道有非阻塞I/O模式

實(shí)現(xiàn)

Java NIO中最常用的通道實(shí)現(xiàn)是如下幾個(gè)抄淑,可以看出跟傳統(tǒng)的 I/O 操作類是一一對(duì)應(yīng)的犀盟。

  • FileChannel:讀寫(xiě)文件
  • DatagramChannel: UDP協(xié)議網(wǎng)絡(luò)通信
  • SocketChannel:TCP協(xié)議網(wǎng)絡(luò)通信
  • ServerSocketChannel:監(jiān)聽(tīng)TCP連接

2.Buffer

NIO中所使用的緩沖區(qū)不是一個(gè)簡(jiǎn)單的byte數(shù)組,而是封裝過(guò)的Buffer類蝇狼,通過(guò)它提供的API阅畴,我們可以靈活的操縱數(shù)據(jù),下面細(xì)細(xì)道來(lái)迅耘。

與Java基本類型相對(duì)應(yīng)贱枣,NIO提供了多種 Buffer 類型,如ByteBuffer颤专、CharBuffer纽哥、IntBuffer等,區(qū)別就是讀寫(xiě)緩沖區(qū)時(shí)的單位長(zhǎng)度不一樣(以對(duì)應(yīng)類型的變量為單位進(jìn)行讀寫(xiě))栖秕。

Buffer中有3個(gè)很重要的變量春塌,它們是理解Buffer工作機(jī)制的關(guān)鍵,分別是

  • capacity (總?cè)萘浚?/li>
  • position (指針當(dāng)前位置)
  • limit (讀/寫(xiě)邊界位置)

Buffer的工作方式跟C語(yǔ)言里的字符數(shù)組非常的像,類比一下只壳,capacity就是數(shù)組的總長(zhǎng)度俏拱,position就是我們讀/寫(xiě)字符的下標(biāo)變量,limit就是結(jié)束符的位置吼句。Buffer初始時(shí)3個(gè)變量的情況如下圖

在對(duì)Buffer進(jìn)行讀/寫(xiě)的過(guò)程中锅必,position會(huì)往后移動(dòng),而 limit 就是 position 移動(dòng)的邊界惕艳。由此不難想象搞隐,在對(duì)Buffer進(jìn)行寫(xiě)入操作時(shí),limit應(yīng)當(dāng)設(shè)置為capacity的大小远搪,而對(duì)Buffer進(jìn)行讀取操作時(shí)劣纲,limit應(yīng)當(dāng)設(shè)置為數(shù)據(jù)的實(shí)際結(jié)束位置。(注意:將Buffer數(shù)據(jù) 寫(xiě)入 通道是Buffer 讀取 操作谁鳍,從通道 讀取 數(shù)據(jù)到Buffer是Buffer 寫(xiě)入 操作)

在對(duì)Buffer進(jìn)行讀/寫(xiě)操作前味廊,我們可以調(diào)用Buffer類提供的一些輔助方法來(lái)正確設(shè)置 position 和 limit 的值,主要有如下幾個(gè)

  • flip(): 設(shè)置 limit 為 position 的值棠耕,然后 position 置為0余佛。對(duì)Buffer進(jìn)行讀取操作前調(diào)用。
  • rewind(): 僅僅將 position 置0窍荧。一般是在重新讀取Buffer數(shù)據(jù)前調(diào)用辉巡,比如要讀取同一個(gè)Buffer的數(shù)據(jù)寫(xiě)入多個(gè)通道時(shí)會(huì)用到勃蜘。
  • clear(): 回到初始狀態(tài)豪硅,即 limit 等于 capacity澳盐,position 置0询吴。重新對(duì)Buffer進(jìn)行寫(xiě)入操作前調(diào)用。
  • compact(): 將未讀取完的數(shù)據(jù)(position 與 limit 之間的數(shù)據(jù))移動(dòng)到緩沖區(qū)開(kāi)頭往毡,并將 position 設(shè)置為這段數(shù)據(jù)末尾的下一個(gè)位置骨坑。其實(shí)就等價(jià)于重新向緩沖區(qū)中寫(xiě)入了這么一段數(shù)據(jù)共屈。

然后输硝,看一個(gè)實(shí)例今瀑,使用 FileChannel 讀寫(xiě)文本文件,通過(guò)這個(gè)例子驗(yàn)證通道可讀可寫(xiě)的特性以及Buffer的基本用法(注意 FileChannel 不能設(shè)置為非阻塞模式)点把。

    FileChannel channel = new RandomAccessFile("test.txt", "rw").getChannel();
    channel.position(channel.size());  // 移動(dòng)文件指針到末尾(追加寫(xiě)入)
    
    ByteBuffer byteBuffer = ByteBuffer.allocate(20);
    
    // 數(shù)據(jù)寫(xiě)入Buffer
    byteBuffer.put("你好橘荠,世界!\n".getBytes(StandardCharsets.UTF_8));
 
    // Buffer -> Channel
    byteBuffer.flip();
    while (byteBuffer.hasRemaining()) {
        channel.write(byteBuffer);
    }
 
    channel.position(0); // 移動(dòng)文件指針到開(kāi)頭(從頭讀壤商印)
    CharBuffer charBuffer = CharBuffer.allocate(10);
    CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
 
    // 讀出所有數(shù)據(jù)
    byteBuffer.clear();
    while (channel.read(byteBuffer) != -1 || byteBuffer.position() > 0) {
        byteBuffer.flip();
 
        // 使用UTF-8解碼器解碼
        charBuffer.clear();
        decoder.decode(byteBuffer, charBuffer, false);
        System.out.print(charBuffer.flip().toString());
 
        byteBuffer.compact(); // 數(shù)據(jù)可能有剩余
    }
    加群895244712哥童,免費(fèi)獲取Java架構(gòu)師進(jìn)階學(xué)習(xí)資料
    channel.close();

這個(gè)例子中使用了兩個(gè)Buffer,其中 byteBuffer 作為通道讀寫(xiě)的數(shù)據(jù)緩沖區(qū)褒翰,charBuffer 用于存儲(chǔ)解碼后的字符贮懈。clear() 和 flip() 的用法正如上文所述匀泊,需要注意的是最后那個(gè) compact() 方法,即使 charBuffer 的大小完全足以容納 byteBuffer 解碼后的數(shù)據(jù)朵你,這個(gè) compact() 也必不可少各聘,這是因?yàn)槌S弥形淖址腢TF-8編碼占3個(gè)字節(jié),因此有很大概率出現(xiàn)在中間截?cái)嗟那闆r撬呢,請(qǐng)看下圖:

當(dāng) Decoder 讀取到緩沖區(qū)末尾的 0xe4 時(shí)伦吠,無(wú)法將其映射到一個(gè) Unicode妆兑,decode()方法第三個(gè)參數(shù) false 的作用就是讓 Decoder 把無(wú)法映射的字節(jié)及其后面的數(shù)據(jù)都視作附加數(shù)據(jù)魂拦,因此 decode() 方法會(huì)在此處停止,并且 position 會(huì)回退到 0xe4 的位置搁嗓。如此一來(lái)芯勘, 緩沖區(qū)中就遺留了“中”字編碼的第一個(gè)字節(jié),必須將其 compact 到前面腺逛,以正確的和后序數(shù)據(jù)拼接起來(lái)荷愕。

BTW,例子中的 CharsetDecoder 也是 Java NIO 的一個(gè)新特性棍矛,所以大家應(yīng)該發(fā)現(xiàn)了一點(diǎn)哈安疗,NIO的操作是面向緩沖區(qū)的(傳統(tǒng)I/O是面向流的)。

至此够委,我們了解了 Channel 與 Buffer 的基本用法荐类。接下來(lái)要說(shuō)的是讓一個(gè)線程管理多個(gè)Channel的重要組件。

3.Selector

Selector 是什么

Selector(選擇器)是一個(gè)特殊的組件茁帽,用于采集各個(gè)通道的狀態(tài)(或者說(shuō)事件)玉罐。我們先將通道注冊(cè)到選擇器,并設(shè)置好關(guān)心的事件潘拨,然后就可以通過(guò)調(diào)用select()方法吊输,靜靜地等待事件發(fā)生。

通道有如下4個(gè)事件可供我們監(jiān)聽(tīng):

  • Accept:有可以接受的連接
  • Connect:連接成功
  • Read:有數(shù)據(jù)可讀
  • Write:可以寫(xiě)入數(shù)據(jù)了

為什么要用Selector

前文說(shuō)了铁追,如果用阻塞I/O季蚂,需要多線程(浪費(fèi)內(nèi)存),如果用非阻塞I/O琅束,需要不斷重試(耗費(fèi)CPU)癣蟋。Selector的出現(xiàn)解決了這尷尬的問(wèn)題,非阻塞模式下狰闪,通過(guò)Selector疯搅,我們的線程只為已就緒的通道工作,不用盲目的重試了埋泵。比如幔欧,當(dāng)所有通道都沒(méi)有數(shù)據(jù)到達(dá)時(shí)罪治,也就沒(méi)有Read事件發(fā)生,我們的線程會(huì)在select()方法處被掛起礁蔗,從而讓出了CPU資源觉义。

使用方法

如下所示,創(chuàng)建一個(gè)Selector浴井,并注冊(cè)一個(gè)Channel晒骇。

注意:要將 Channel 注冊(cè)到 Selector,首先需要將 Channel 設(shè)置為非阻塞模式磺浙,否則會(huì)拋異常洪囤。

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

register()方法的第二個(gè)參數(shù)名叫“interest set”,也就是你所關(guān)心的事件集合撕氧。如果你關(guān)心多個(gè)事件瘤缩,用一個(gè)“按位或運(yùn)算符”分隔,比如

SelectionKey.OP_READ | SelectionKey.OP_WRITE

這種寫(xiě)法一點(diǎn)都不陌生伦泥,支持位運(yùn)算的編程語(yǔ)言里都這么玩剥啤,用一個(gè)整型變量可以標(biāo)識(shí)多種狀態(tài),它是怎么做到的呢不脯,其實(shí)很簡(jiǎn)單府怯,舉個(gè)例子,首先預(yù)定義一些常量防楷,它們的值(二進(jìn)制)如下

可以發(fā)現(xiàn)牺丙,它們值為1的位都是錯(cuò)開(kāi)的,因此對(duì)它們進(jìn)行按位或運(yùn)算之后得出的值就沒(méi)有二義性域帐,可以反推出是由哪些變量運(yùn)算而來(lái)赘被。怎么判斷呢,沒(méi)錯(cuò)肖揣,就是“按位與”運(yùn)算民假。比如,現(xiàn)在有一個(gè)狀態(tài)集合變量值為 0011龙优,我們只需要判斷 "0011 & OP_READ" 的值是 1 還是 0 就能確定集合是否包含 OP_READ 狀態(tài)羊异。

然后,注意 register() 方法返回了一個(gè)SelectionKey的對(duì)象彤断,這個(gè)對(duì)象包含了本次注冊(cè)的信息野舶,我們也可以通過(guò)它修改注冊(cè)信息。從下面完整的例子中可以看到宰衙,select()之后平道,我們也是通過(guò)獲取一個(gè) SelectionKey 的集合來(lái)獲取到那些狀態(tài)就緒了的通道。

一個(gè)完整實(shí)例

概念和理論的東西闡述完了(其實(shí)寫(xiě)到這里供炼,我發(fā)現(xiàn)沒(méi)寫(xiě)出多少東西一屋,好尷尬(⊙?⊙))窘疮,看一個(gè)完整的例子吧。

這個(gè)例子使用Java NIO實(shí)現(xiàn)了一個(gè)單線程的服務(wù)端冀墨,功能很簡(jiǎn)單闸衫,監(jiān)聽(tīng)客戶端連接,當(dāng)連接建立后诽嘉,讀取客戶端的消息蔚出,并向客戶端響應(yīng)一條消息。

需要注意的是虫腋,我用字符 '\0'(一個(gè)值為0的字節(jié)) 來(lái)標(biāo)識(shí)消息結(jié)束骄酗。

單線程Server

public class NioServer {
    
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建一個(gè)selector
        Selector selector = Selector.open();
        
        // 初始化TCP連接監(jiān)聽(tīng)通道
        ServerSocketChannel listenChannel = ServerSocketChannel.open();
        listenChannel.bind(new InetSocketAddress(9999));
        listenChannel.configureBlocking(false);
        // 注冊(cè)到selector(監(jiān)聽(tīng)其ACCEPT事件)
        listenChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        // 創(chuàng)建一個(gè)緩沖區(qū)
        ByteBuffer buffer = ByteBuffer.allocate(100);
        
        while (true) {
            selector.select(); //阻塞,直到有監(jiān)聽(tīng)的事件發(fā)生
            Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
            
            // 通過(guò)迭代器依次訪問(wèn)select出來(lái)的Channel事件
            while (keyIter.hasNext()) {
                SelectionKey key = keyIter.next();
                
                if (key.isAcceptable()) { // 有連接可以接受
                    SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                    
                    System.out.println("與【" + channel.getRemoteAddress() + "】建立了連接岔乔!");
                    
                } else if (key.isReadable()) { // 有數(shù)據(jù)可以讀取
                    buffer.clear();
    
                    // 讀取到流末尾說(shuō)明TCP連接已斷開(kāi)酥筝,
                    // 因此需要關(guān)閉通道或者取消監(jiān)聽(tīng)READ事件
                    // 否則會(huì)無(wú)限循環(huán)
                    if (((SocketChannel) key.channel()).read(buffer) == -1) {
                        key.channel().close();
                        continue;
                    } 
                    
                    // 按字節(jié)遍歷數(shù)據(jù)
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        byte b = buffer.get();
                        
                        if (b == 0) { // 客戶端消息末尾的\0
                            System.out.println();
                            
                            // 響應(yīng)客戶端
                            buffer.clear();
                            buffer.put("Hello, Client!\0".getBytes());
                            buffer.flip();
                            while (buffer.hasRemaining()) {
                                ((SocketChannel) key.channel()).write(buffer);
                            }
                        } else {
                            System.out.print((char) b);
                        }
                    }
                }
                
                // 已經(jīng)處理的事件一定要手動(dòng)移除
                keyIter.remove();
            }
        }
    }
}

Client

這個(gè)客戶端純粹測(cè)試用滚躯,為了看起來(lái)不那么費(fèi)勁雏门,就用傳統(tǒng)的寫(xiě)法了,代碼很簡(jiǎn)短掸掏。

要嚴(yán)謹(jǐn)一點(diǎn)測(cè)試的話茁影,應(yīng)該并發(fā)運(yùn)行大量Client,統(tǒng)計(jì)服務(wù)端的響應(yīng)時(shí)間丧凤,而且連接建立后不要立刻發(fā)送數(shù)據(jù)募闲,這樣才能發(fā)揮出服務(wù)端非阻塞I/O的優(yōu)勢(shì)。

public class Client {
    
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 9999);
        InputStream is = socket.getInputStream();
        OutputStream os = socket.getOutputStream();
 
        // 先向服務(wù)端發(fā)送數(shù)據(jù)
        os.write("Hello, Server!\0".getBytes());
        
        // 讀取服務(wù)端發(fā)來(lái)的數(shù)據(jù)
        int b;
        while ((b = is.read()) != 0) {
            System.out.print((char) b);
        }
        System.out.println();
        
        socket.close();
    }
}

NIO vs IO

學(xué)習(xí)了NIO之后我們都會(huì)有這樣一個(gè)疑問(wèn):到底什么時(shí)候該用NIO愿待,什么時(shí)候該用傳統(tǒng)的I/O呢浩螺?

其實(shí)了解他們的特性后,答案還是比較明確的仍侥,NIO擅長(zhǎng)1個(gè)線程管理多條連接要出,節(jié)約系統(tǒng)資源,但是如果每條連接要傳輸?shù)臄?shù)據(jù)量很大的話农渊,因?yàn)槭峭絀/O患蹂,會(huì)導(dǎo)致整體的響應(yīng)速度很慢;而傳統(tǒng)I/O為每一條連接創(chuàng)建一個(gè)線程砸紊,能充分利用處理器并行處理的能力传于,但是如果連接數(shù)量太多,內(nèi)存資源會(huì)很緊張醉顽。加群895244712沼溜,免費(fèi)獲取Java架構(gòu)師進(jìn)階學(xué)習(xí)資料

總結(jié)就是:連接數(shù)多數(shù)據(jù)量小用NIO,連接數(shù)少用I/O(寫(xiě)起來(lái)也簡(jiǎn)單- -)游添。

Next

經(jīng)過(guò)NIO核心組件的學(xué)習(xí)系草,了解了非阻塞服務(wù)端實(shí)現(xiàn)的基本方法弹惦。然而,細(xì)心的你們肯定也發(fā)現(xiàn)了悄但,上面那個(gè)完整的例子棠隐,實(shí)際上就隱藏了很多問(wèn)題。比如檐嚣,例子中只是簡(jiǎn)單的將讀取到的每個(gè)字節(jié)輸出助泽,實(shí)際環(huán)境中肯定是要讀取到完整的消息后才能進(jìn)行下一步處理,由于NIO的非阻塞特性嚎京,一次可能只讀取到消息的一部分嗡贺,這已經(jīng)很糟糕了,如果同一條連接會(huì)連續(xù)發(fā)來(lái)多條消息鞍帝,那不僅要對(duì)消息進(jìn)行拼接诫睬,還需要切割,同理帕涌,例子中給客戶端響應(yīng)的時(shí)候摄凡,用了個(gè)while()循環(huán),保證數(shù)據(jù)全部write完成再做其它工作蚓曼,實(shí)際應(yīng)用中為了性能亲澡,肯定不會(huì)這么寫(xiě)。另外纫版,為了充分利用現(xiàn)代處理器多核心并行處理的能力床绪,應(yīng)該用一個(gè)線程組來(lái)管理這些連接的事件。

要解決這些問(wèn)題其弊,需要一個(gè)嚴(yán)謹(jǐn)而繁瑣的設(shè)計(jì)癞己,不過(guò)幸運(yùn)的是,我們有開(kāi)源的框架可用梭伐,那就是優(yōu)雅而強(qiáng)大的Netty痹雅,Netty基于Java NIO,提供異步調(diào)用接口籽御,開(kāi)發(fā)高性能服務(wù)器的一個(gè)很好的選擇练慕,之前在項(xiàng)目中使用過(guò),但沒(méi)有深入學(xué)習(xí)技掏,打算下一步好好學(xué)學(xué)它铃将,到時(shí)候再寫(xiě)一篇筆記。

Java NIO設(shè)計(jì)的目標(biāo)是為程序員提供API以享受現(xiàn)代操作系統(tǒng)最新的I/O機(jī)制哑梳,所以覆蓋面較廣劲阎,除了文中所涉及的組件與特性,還有很多其它的鸠真,比如 Pipe(管道)悯仙、Path(路徑)龄毡、Files(文件) 等,有的是用于提升I/O性能的新組件锡垄,有的是簡(jiǎn)化I/O操作的工具沦零,具體用法可以參看最后 References 里的鏈接。

References

[1] Differences Between Synchronous and Asynchronous I/O

[2] Java NIO - Wikipedia

[3] Java NIO Tutorial

[4] Package java.nio

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末货岭,一起剝皮案震驚了整個(gè)濱河市路操,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌千贯,老刑警劉巖屯仗,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異搔谴,居然都是意外死亡魁袜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)敦第,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)峰弹,“玉大人,你說(shuō)我怎么就攤上這事申尼】遄浚” “怎么了垫桂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵师幕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我诬滩,道長(zhǎng)霹粥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任疼鸟,我火速辦了婚禮后控,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘空镜。我一直安慰自己浩淘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布吴攒。 她就那樣靜靜地躺著张抄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪洼怔。 梳的紋絲不亂的頭發(fā)上署惯,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音镣隶,去河邊找鬼极谊。 笑死诡右,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的轻猖。 我是一名探鬼主播帆吻,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咙边!你這毒婦竟也來(lái)了桅锄?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤样眠,失蹤者是張志新(化名)和其女友劉穎友瘤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體檐束,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辫秧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了被丧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盟戏。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖甥桂,靈堂內(nèi)的尸體忽然破棺而出柿究,到底是詐尸還是另有隱情,我是刑警寧澤黄选,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布蝇摸,位于F島的核電站,受9級(jí)特大地震影響办陷,放射性物質(zhì)發(fā)生泄漏貌夕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一民镜、第九天 我趴在偏房一處隱蔽的房頂上張望啡专。 院中可真熱鬧,春花似錦制圈、人聲如沸们童。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)慧库。三九已至,卻和暖如春亥鬓,著一層夾襖步出監(jiān)牢的瞬間完沪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留覆积,地道東北人听皿。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宽档,于是被迫代替她去往敵國(guó)和親尉姨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容