• Java NIO系列教程(十二) Java NIO與IO

    原文地址:http://tutorials.jenkov.com/java-nio/nio-vs-io.html

    作者:Jakob Jenkov ??譯者:郭蕾 ? ?校對:方騰飛

    當學習了Java NIO和IO的API后,一個問題馬上涌入腦海:

    我應該何時使用IO,何時使用NIO呢?在本文中,我會盡量清晰地解析Java NIO和IO的差異、它們的使用場景,以及它們如何影響您的代碼設計。

    Java NIO和IO的主要區別

    下表總結了Java NIO和IO之間的主要差別,我會更詳細地描述表中每部分的差異。

    IO? ? ? ? ? ? ? ? NIO
    面向流 ? ? ? ? ? ?面向緩沖
    阻塞IO ? ? ? ? ?  非阻塞IO
    無                選擇器

    面向流與面向緩沖

    Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩沖區的。?Java IO面向流意味著每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前后移動流中的數據。如果需要前后移動從流中讀取的數據,需要先將它緩存到一個緩沖區。 Java NIO的緩沖導向方法略有不同。數據讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩沖區時,不要覆蓋緩沖區里尚未處理的數據。

    阻塞與非阻塞IO

    Java IO的各種流是阻塞的。這意味著,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再干任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。

    選擇器(Selectors

    Java NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以注冊多個通道使用一個選擇器,然后使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

    NIO和IO如何影響應用程序的設計

    無論您選擇IO或NIO工具箱,可能會影響您應用程序設計的以下幾個方面:

    1. ?對NIO或IO類的API調用。
    2. 數據處理。
    3. 用來處理數據的線程數。

    API調用

    當然,使用NIO的API調用時看起來與使用IO時有所不同,但這并不意外,因為并不是僅從一個InputStream逐字節讀取,而是數據必須先讀入緩沖區再處理。

    數據處理

    使用純粹的NIO設計相較IO設計,數據處理也受到影響。

    在IO設計中,我們從InputStream或 Reader逐字節讀取數據。假設你正在處理一基于行的文本數據流,例如:

    Name: Anna
    Age: 25
    Email: anna@mailserver.com
    Phone: 1234567890

    該文本行的流可以這樣處理:
    InputStream input = … ; // get the InputStream from the client socket

    BufferedReader reader = new BufferedReader(new InputStreamReader(input));
    
    String nameLine   = reader.readLine();
    String ageLine    = reader.readLine();
    String emailLine  = reader.readLine();
    String phoneLine  = reader.readLine();
    

    請注意處理狀態由程序執行多久決定。換句話說,一旦reader.readLine()方法返回,你就知道肯定文本行就已讀完, readline()阻塞直到整行讀完,這就是原因。你也知道此行包含名稱;同樣,第二個readline()調用返回的時候,你知道這行包含年齡等。 正如你可以看到,該處理程序僅在有新數據讀入時運行,并知道每步的數據是什么。一旦正在運行的線程已處理過讀入的某些數據,該線程不會再回退數據(大多如此)。下圖也說明了這條原則: Java IO: 從一個阻塞的流中讀數據) 而一個NIO的實現會有所不同,下面是一個簡單的例子:

    ByteBuffer buffer = ByteBuffer.allocate(48);
    
    int bytesRead = inChannel.read(buffer);
    

    注意第二行,從通道讀取字節到ByteBuffer。當這個方法調用返回時,你不知道你所需的所有數據是否在緩沖區內。你所知道的是,該緩沖區包含一些字節,這使得處理有點困難。
    假設第一次 read(buffer)調用后,讀入緩沖區的數據只有半行,例如,“Name:An”,你能處理數據嗎?顯然不能,需要等待,直到整行數據讀入緩存,在此之前,對數據的任何處理毫無意義。

    所以,你怎么知道是否該緩沖區包含足夠的數據可以處理呢?好了,你不知道。發現的方法只能查看緩沖區中的數據。其結果是,在你知道所有數據都在緩沖區里之前,你必須檢查幾次緩沖區的數據。這不僅效率低下,而且可以使程序設計方案雜亂不堪。例如:

    ByteBuffer buffer = ByteBuffer.allocate(48);
    
    int bytesRead = inChannel.read(buffer);
    
    while(! bufferFull(bytesRead) ) {
    
    bytesRead = inChannel.read(buffer);
    
    }
    

    bufferFull()方法必須跟蹤有多少數據讀入緩沖區,并返回真或假,這取決于緩沖區是否已滿。換句話說,如果緩沖區準備好被處理,那么表示緩沖區滿了。

    bufferFull()方法掃描緩沖區,但必須保持在bufferFull()方法被調用之前狀態相同。如果沒有,下一個讀入緩沖區的數據可能無法讀到正確的位置。這是不可能的,但卻是需要注意的又一問題。

    如果緩沖區已滿,它可以被處理。如果它不滿,并且在你的實際案例中有意義,你或許能處理其中的部分數據。但是許多情況下并非如此。下圖展示了“緩沖區數據循環就緒”:

    Java NIO:從一個通道里讀數據,直到所有的數據都讀到緩沖區里.

    3) 用來處理數據的線程數

    NIO可讓您只使用一個(或幾個)單線程管理多個通道(網絡連接或文件),但付出的代價是解析數據可能會比從一個阻塞流中讀取數據更復雜。

    如果需要管理同時打開的成千上萬個連接,這些連接每次只是發送少量的數據,例如聊天服務器,實現NIO的服務器可能是一個優勢。同樣,如果你需要維持許多打開的連接到其他計算機上,如P2P網絡中,使用一個單獨的線程來管理你所有出站連接,可能是一個優勢。一個線程多個連接的設計方案如下圖所示:

    Java NIO: 單線程管理多個連接

    如果你有少量的連接使用非常高的帶寬,一次發送大量的數據,也許典型的IO服務器實現可能非常契合。下圖說明了一個典型的IO服務器設計:

    Java IO: 一個典型的IO服務器設計- 一個連接通過一個線程處理.

    原創文章,轉載請注明: 轉載自并發編程網 – www.okfdzs91.com本文鏈接地址: Java NIO系列教程(十二) Java NIO與IO


    FavoriteLoading添加本文到我的收藏
    • Trackback 關閉
    • 評論 (37)
    1. 贊一個 寫得不錯!

      • ℡actionフ
      • 2013/05/07 4:38下午

      下面的兩個圖有什么區別

        • 匿名
        • 2013/11/16 4:56下午

        同問

          • gongweixin
          • 2014/02/27 5:48下午

          圖畫的不是太好 ,上面的圖是說 多個connection 對應一個thread ,下面的圖是說 每個connection 對應一個thread , 根據我的理解圖這么畫會好點、
          |——————|
          | Thread |
          |————-| |——————|
          | Thread |———————->| |————| |
          |————-| | | Connection | |
          |ServerSocket | | |————| |
          |————-| | | Connection | |
          | |————| |
          |——————|

          ====================================================================
          |——————|
          | Thread |
          |————-| |——————|
          | Thread |———————->| Connection |
          |————-| |——————|
          |ServerSocket |
          |————-|———————->|——————|
          | Thread |
          |——————|
          | Connection |
          |——————|

          • gongweixin
          • 2014/02/27 5:55下午

          圖畫的不是太好 ,上面的圖是說 多個connection 對應一個thread ,下面的圖是說 每個connection 對應一個thread , 根據我的理解圖這么畫會好點、
          |==================|
          | Thread |
          |=============| |==================|
          | Thread |———————->| |============| |
          |=============| | | Connection | |
          |ServerSocket | | |============| |
          |=============| | | Connection | |
          | |============| |
          |==================|
          +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          |==================|
          | Thread |
          |=============| |==================|
          | Thread |======================>| Connection |
          |=============| |==================|
          |ServerSocket |
          |=============|======================>|==================|
          | Thread |
          |==================|
          | Connection |
          |==================|

    2. hi, 讀完了關于 nio 的文章。 請問有沒有相關的全面點的 demo 能更深刻的理解?

    3. is-data-in-buffer-ready loop 翻譯成“緩沖區數據循環就緒”,合適嗎?

      • gongweixin
      • 2014/02/27 5:56下午

      夕水溪下 :
      截圖吧

      不會啊 、

      • 夏末冬初
      • 2014/03/14 6:39下午

      Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。

      沒有數據可讀之前做別的事情,這個別的事情泛指哪些事情,假如我這個線程就需要讀出的所有數據,然后進行打印,那這時候打印出來的是部分數據,還是所有數據。

        • 木槿花蕭
        • 2014/08/31 3:42下午

        你可以寫個回調或者Future來把數據讀取完之后的處理代碼放進去,然后可以不等待數據處理完就可以做任何事情,比如繼續監聽客戶端請求之類的。 但總感覺NIO是以增加了數據處理復雜度來換取cpu的時間。

      • 夏末冬初
      • 2014/03/14 7:07下午

      ByteBuffer buffer = ByteBuffer.allocate(48);
      int bytesRead = inChannel.read(buffer);
      while(! bufferFull(bytesRead) ) {
      bytesRead = inChannel.read(buffer);
      }

      一定要這樣不斷的輪訓檢查嗎,這樣效率貌似和阻塞io沒有啥區別啊。而且什么時候判斷數據是完整的呢?

        • 木槿花蕭
        • 2014/08/31 3:54下午

        int bytesRead = inChannel.read(buffer); 這里返回的字節數是-1的時候證明你已經讀完了,前幾篇文章有寫。至于有沒有讀取到完整數據需要你自己的代碼控制好。第三章通道的示例里面有寫:http://www.okfdzs91.com/channels/

    4. 為什么NIO是同步非阻塞的? 同步是哪里同步?阻塞是哪里阻塞?

      • NIO的應用場景:多連接小數據。你可以這樣認為,線程處理完某一連接的一部分數據之后,下一部分數據還沒到,這樣,線程有時間先去處理另一個連接的數據。樓主的例子中沒有體現多連接的情況。

        • terryg
        • 2017/01/17 11:48下午

        NIO的同步應該是說的NIO的底層實現,有同步非阻塞和異步非阻塞兩種實現方式,參見:http://www.artima.com/articles/io_design_patterns.html

      • 在路上
      • 2014/09/30 11:48上午

      讀完整個系列,對Nio有了個大概的了解,十分感謝!

    5. 讀完整個系列,對NIO有大致的了解了,非常感謝!但是,請問,有沒有一個比較完善點的例子,能體現NIO在實際工作中的運用嗎?目前,對NIO的優勢持疑惑狀態,不能體會它的優點,望解答~

      • 可以對比使用下Java IO ,使用起來NIO比較方便,有更多更高級的API,比如transferTo三行代碼就能實現從一個文件讀取數據到另外一個文件。

    6. nio既然是異步的, 那有沒有回調函數或者defer函數呢?

      • '4 ever love
      • 2014/11/06 5:18下午

      bufferFull()方法掃描緩沖區,但必須保持在bufferFull()方法被調用之前狀態相同。如果沒有,下一個讀入緩沖區的數據可能無法讀到正確的位置。這是不可能的,但卻是需要注意的又一問題。

      這一段翻譯有問題,應該搞成:

      通過bufferFull()方法掃描緩沖區,緩沖區必須保持和bufferFull()方法被調用之前相同的狀態,如果不是,下一個讀入緩沖區的數據可能無法被讀到正確的位置,這并非不可能,但也暴露出另一個需要我們注意的問題

      • hequn
      • 2015/01/06 3:07下午

      “這是不可能的,但卻是需要注意的又一問題?!?應該是“這不是不可能的,但卻是需要注意的又一問題?!痹腡his is not impossible, but it is yet another issue to watch out for.

      • 雨藍
      • 2015/04/25 5:35下午

      nio處理多而小的連接,用了緩沖區,那么會不會時時性不好了呢?

      • jk1420
      • 2015/06/09 5:00下午

      圖片都壞了,看不到

      • sstong123
      • 2015/07/06 10:21上午

      好文章,但文中圖片都不能顯示,樓主可不可以補一下?

      • hl174
      • 2016/04/26 4:02下午

      bufferFull

      怎么沒有發現這個方法

      • hl174
      • 2016/04/26 4:07下午

      話說仔細看了這一些列12期的nio文章,還是不知道具體怎么用,有沒有深點的應用型的文章

      • jzh0535
      • 2016/06/27 7:20下午

      mark!

      • Yancy
      • 2016/09/16 10:25上午

      圖加載不了

      • bw
      • 2016/11/03 1:39下午

      這些圖怎么都不顯示啦?

      • lin_1220
      • 2017/01/12 10:57上午

      樓主,您好,里面的圖全都看不到

      • adeline
      • 2017/12/22 4:23下午

      看不到圖

      • red_and_black
      • 2018/06/07 11:11上午

      樓主,哪里有圖啊,沒有圖啊,原文中倒是有圖

      • menfre
      • 2018/09/15 2:46下午

      “bufferFull()方法掃描緩沖區,但必須保持在bufferFull()方法被調用之前狀態相同。如果沒有,下一個讀入緩沖區的數據可能無法讀到正確的位置。這是不可能的,但卻是需要注意的又一問題。”

      這里是說保持bufferFull()調用前后的緩沖區狀態(position/limit)是不可能的嗎? 可以通過檢查前 mark position的位置,在檢查完成通過reset來保證檢查前后緩沖區的狀態一致。

      • Arry0624
      • 2019/01/11 4:54下午

      QAQ~學習學習

    您必須 登陸 后才能發表評論

    return top

    龙之彩彩票 o9a| eay| 7qm| ks7| ymw| g88| qsc| u8o| c8o| uwc| 8im| mo8| qsm| g6q| cog| 7ce| kk7| qci| o7o| cqk| 7ek| ccy| es7| guo| m8m| uwa| 6ui| ka6| sqy| y6y| ego| 6ak| ck6| koo| uge| mm7| ssa| y5w| qeo| 5kg| am5| aom| w5y| egq| 6wq| cc6| ooi| a6q| a4u| qoi| 4qm| cq4| mke| k5i| ssa| 5ge| uc5| guc| q5g| yag| 5gm| 3ge| moi| 4mw| yk4| ssc| y4k| mas| 4ea| mc4| wyu| m5e| kiq| 3ci| 3yg| gu3| oau| i3u| aao| 3ey| gq4| gwq| k4i| qey| 2sk| kk2| qgm| kag| q2u| gum|