Java中BIO,NIO,AIO總結

Java中BIO,NIO,AIO總結

三種IO模式適用場景

1.BIO方式適用於鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發有侷限性,JDK1.4之前是惟一的選擇,好處是編碼實現方式簡單,且也容易理解。2.NIO方式適用於鏈接數目多且鏈接比較短的架構,好比聊天服務器,彈幕系統等,相比BIO編碼較複雜,JDK1.4之後開始支持。3.AIO方式適用於鏈接數據多且鏈接較長的場景,好比相冊服務器等,編程較複雜,JDK1.7纔開始支持。目前好像並未獲得普遍使用。java

BIO(blocking I/O) 基本介紹

Java BIO是傳統的Java io編程,相關的類和接口在http://java.io包中編程

Java BIO:同步阻塞,一個鏈接爲一個線程,鏈接一個客戶端就須要啓動一個線程進行處理,若是鏈接未斷開且未作任何事,會形成沒必要要的開銷。能夠經過線程池優化。windows

Java BIO:適用於鏈接數目較小且相對固定的架構,對服務器的要求比較高,對併發有侷限性JDK1.4之前惟一的選擇,簡單易理解。api

BIO的原理示意圖


9c68dc874234b903a30df85d618a5ac3.jpeg


流程

1.服務器啓動ServerSoket。2.客戶端啓動Socket與服務器通訊,默認狀況下服務器須要對每一個客戶端創建一個線程與之通訊。3.客戶端發出請求與服務器通訊。4.若是請求成功,客戶端會等待請求結束後繼續執行。數組

Java BIO應用實例

實例要求:緩存

1.使用NIO編寫服務端,監聽8888端口號,當有客戶端鏈接時,啓動一個線程與之通訊。2.使用線程池改進,能夠鏈接多個客戶端。服務器

服務端

package com.crazy.io.bio;import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/*** @author shiyi* @version 1.0* @date 2020-7-7 23:48*/public class BIOServer {

   public static void main(String[] args) throws IOException {
       /**        * 1.建立一個線程池        * 若是有客戶端鏈接了,就建立一個線程與之通訊。        */
       ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

       // 建立ServerSocket       ServerSocket serverSocket = new ServerSocket(6668);

       System.out.println("服務器啓動了");

       while (true) {
           // 監聽,等待客戶端鏈接           final Socket socket = serverSocket.accept();
           System.out.println("鏈接到了一個客戶端");
           newCachedThreadPool.execute(new Runnable() {
               @Override
               public void run() {
                   // 能夠和客戶端通信                   handler(socket);
               }
           });
       }
   }

   // 編寫一個handler方法,與客戶端通信   public static void handler(Socket socket) {
       // 經過socket獲取輸入流       try {
           System.out.println("線程信息 id=" + Thread.currentThread().getId() + "線程名字=" + Thread.currentThread().getName());
           InputStream inputStream = socket.getInputStream();
           byte[] bytes = new byte[1024];

           // 循環的讀取客戶端發送的數據           while (true) {
               System.out.println("線程信息 id=" + Thread.currentThread().getId() + "線程名字=" + Thread.currentThread().getName());

               int read = inputStream.read(bytes);
               if (read != -1) {
                   // 輸出客戶端發送的數據                   System.out.println(new String(bytes, 0, read));
               } else {
                   break;
               }
           }
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           // 關閉和客戶端的鏈接           try {
               socket.close();
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
   }}

客戶端網絡

package com.crazy.io.zerocopy;importjava.io.DataOutputStream;importjava.io.FileInputStream;importjava.io.InputStream;importjava.net.Socket;

 public class OldIOClient {
   public static void main(String[] args) throws Exception {
     Socket socket = new Socket("localhost", 6668);

     String fileName = "1.txt";
     InputStream inputStream = new FileInputStream(fileName);
     DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
     byte[] buffer = new byte[4096];
     long readCount;
     long total = 0;

     long startTime = System.currentTimeMillis();

     while ((readCount = inputStream.read(buffer)) >= 0) {
         total += readCount;
         dataOutputStream.write(buffer);
     }
     System.out.println("發送總字節數: " + total + ", 耗時: " + (System.currentTimeMillis() - startTime));
     dataOutputStream.close();
     socket.close();
     inputStream.close();
 }}

有小夥伴說,我就只想寫服務器,不想寫客戶端,能不能測試。能,我都給大家準備好了。可使用windows的命令行telnet命令來測試。多線程

telnet 127.0.0.1 6668

鏈接成功後經過按 Ctrl+] 符號進入發送數據界面架構

a43caeb45bbedd1040830259988b6652.jpeg

send HelloWorld

f60fdc97628bdd74c88ffc9e99820609.jpeg

查看服務端收到的消息

b229568c8a450b0bd1bc77c8406046a2.jpeg

完成了簡單的以BIO實現的客戶端與服務器之間的交互。

Java BIO問題

1.每一個請求都須要建立獨立的線程。

2.當併發量大時,須要建立大量線程,佔用系統資源。

3.鏈接創建後,若是當前線程暫時沒有數據可讀,則線程就阻塞在 Read 操做上,形成線程資源浪費

Java NIO( java non-blocking IO) 基本介紹

1.Java NIO全稱(java non-blocking io),從 JDK1.4 開始,Java 提供了一系列改進的輸入/輸出的新特性,被統稱爲 NIO(即 New IO),是同步非阻塞的。

2.NIO 相關類都被放在 java.nio 包及子包下,而且對原 http://java.io 包中的不少類進行了改寫。

3.NIO 有三大核心部分:Channel(通道),Buffer(緩衝區), Selector(選擇器) 。

4.NIO是 面向緩衝區。數據讀取到一個它稍後處理的緩衝區中,須要時可在緩衝區中先後移動,這就增長了處理過程當中的靈活性,使用它能夠提供非阻塞式的高伸縮性網絡。

5.Java NIO的非阻塞模式,使一個線程從某通道發送請求或者讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用,就什麼都不獲取,會繼續保持線程阻塞,直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。非阻塞寫也是如此,一個線程請求寫入一些數據到某通道,但不須要等待它徹底寫入,這個線程同時能夠去作別的事情。

6.通俗理解:NIO是能夠作到用一個線程來處理多個操做。假設有500個請求過來,根據實際狀況,能夠分配50或者100個線程來處理。而BIO可能須要建立500個線程來處理數據。

7.NIO的併發請求要遠遠大於BIO。

Selector 、 Channel 和 Buffer 的關係圖


ae6a0b644470c40d589ce36c65fa3448.jpeg


關係圖說明

1.每一個channel 都會對應一個Buffer。

2.Selector 對應一個線程, 一個線程對應多個channel(鏈接)。

3.該圖反應了有三個channel 註冊到 該selector 程序。

4.程序切換到哪一個channel 是有事件決定的, Event 就是一個重要的概念。

5.Selector 會根據不一樣的事件,在各個通道上切換。

6.Buffer 就是一個內存塊 , 底層是有一個數組。

7.數據的讀取寫入是經過Buffer, 這個和BIO , BIO 中要麼是輸入流,或者是輸出流, 不能雙向,可是 NIO的Buffer 是能夠讀也能夠寫, 須要 flip 方法切換。

8.channel 是雙向的, 能夠返回底層操做系統的狀況, 好比Linux , 底層的操做系統通道就是雙向的。

什麼是緩衝區(Buffer)?

緩衝區本質上是一個能夠讀寫數據的內存塊,能夠理解成是一個容器對象(含數

組),該對象提供了一組方法,能夠更輕鬆地使用內存塊,緩衝區對象內置了一

些機制,可以跟蹤和記錄緩衝區的狀態變化狀況。Channel 提供從文件、網絡讀

取數據的渠道,可是讀取或寫入的數據都必須經由 Buffer。

d9e17a84dbe20d0b97933fe8c9380c42.jpeg


Buffer經常使用方法解析


7c9a74c7a1cb81442435732000d9b65f.jpeg

經常使用的ByteBuffer是一個抽象類,繼承Buffer,實現了Comparable接口。

e196fac8a1d59708857cfcfa5518aaa4.jpeg

1. mark:標記。

2. Position:位置,下一個要被讀或寫的元素的索引,每次讀寫緩衝區數據時都會改變改值,爲下次讀寫做準備。

3. Limit:表示緩衝區的當前終點,不能對緩衝區超過極限的位置進行讀寫操做。且極限是能夠修改的。

4. Capacity:容量,便可以容納的最大數據量;在緩衝區建立時被設定而且不能改變。

ByteBuffer中經常使用的方法:

// 緩衝區建立相關apipublic static ByteBuffer allocateDirect(int capacity)//建立直接緩衝區public static ByteBuffer allocate(int capacity)//設置緩衝區的初始容量public static ByteBuffer wrap(byte[] array)//把一個數組放到緩衝區中使用//構造初始化位置offset和上界length的緩衝區public static ByteBuffer wrap(byte[] array,int offset, int length)//緩存區存取相關APIpublic abstract byte get( );//從當前位置position上get,get以後,position會自動+1public abstract byte get (int index);//從絕對位置getpublic abstract ByteBuffer put (byte b);//從當前位置上添加,put以後,position會自動+1public abstract ByteBuffer put (int index, byte b);//從絕對位置上put

通道基本介紹

1.通道能夠同時進行讀寫,而流只能讀或者只能寫。

2.通道能夠實現異步讀寫數據。

3.通道能夠從緩衝讀數據,也能夠寫數據到緩衝。

4c6837d6e60a416bd1b0393abfe5a99c.jpeg

通道說明

7c74ed42d4cc9b2518ee5e77b4a10244.jpeg

Channel是一個接口。

1.經常使用的 Channel 類有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。

2.ServerSocketChanne 相似 ServerSocket 。

3.SocketChannel 相似 Socket。

應用實例

實例要求:

1.把1.txt中的文件讀取到2.txt中。

public class NIOFileChannel03 {
    public static void main(String[] args) throws IOException {
      FileInputStream fileInputStream = newFileInputStream("1.txt");
      FileChannel inputStreamChannel =fileInputStream.getChannel();
      FileOutputStream fileOutputStream = newFileOutputStream("2.txt");
      FileChannel outputStreamChannel =fileOutputStream.getChannel();
      ByteBuffer byteBuffer = ByteBuffer.allocate(10);
      while (true) {
          byteBuffer.clear();
          int read = inputStreamChannel.read(byteBuffer);
          System.out.println("read =" + read);
          if (read == -1) {
              break;
          }
          byteBuffer.flip();
          outputStreamChannel.write(byteBuffer);
        }
        fileInputStream.close();
        inputStreamChannel.close();
        outputStreamChannel.close();
        fileOutputStream.close();
    }}

Buffer和Channel的注意事項

1.ByteBuffer 支持類型化的put 和 get, put 放入的是什麼數據類型,get就應該使用相應的數據類型來取出,不然可能有 BufferUnderflowException 異常。

2.能夠將一個普通Buffer 轉成只讀Buffer。

3.NIO 還提供了 MappedByteBuffer, 可讓文件直接在內存(堆外的內存)中進行修改, 而如何同步到文件由NIO 來完成。

Selector(選擇器)

1.Java 的 NIO,用非阻塞的 IO 方式。能夠用一個線程,處理多個的客戶端鏈接,就會使用到Selector(選擇器)。2.Selector 可以檢測多個註冊的通道上是否有事件發生,若是有事件發生,便獲取事件而後針對每一個事件進行相應的處理。這樣就能夠只用一個單線程去管理多個通道,也就是管理多個鏈接和請求。3.只有在 鏈接/通道 真正有讀寫事件發生時,纔會進行讀寫,就大大地減小了系統開銷,而且沒必要爲每一個鏈接都建立一個線程,不用去維護多個線程。4.避免了多線程之間的上下文切換致使的開銷。

Selector的示意圖


2406f7de07def15764f8b57621d6e9d8.jpg


1.線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程能夠進行其餘任務。

2.線程一般將非阻塞 IO 的空閒時間用於在其餘通道上執行 IO 操做,因此單獨的線程能夠管理多個輸入和輸出通道。

3.因爲讀寫操做都是非阻塞的,這就能夠充分提高 IO 線程的運行效率,避免因爲頻繁 I/O 阻塞致使的線程掛起。

4.一個 I/O 線程能夠併發處理 多個客戶端鏈接和讀寫操做,這從根本上解決了傳統同步阻塞 I/O 一個鏈接一個線程模型,架構的性能、彈性伸縮能力和可靠性都獲得了極大的提高。

5.客戶端鏈接時,會經過ServerSocketChannel 獲得 SocketChannel Selector 進行監聽 select 方法, 返回有事件發生的通道的個數。

6.將socketChannel註冊到Selector上, register(Selector sel, int ops), 一個selector上能夠註冊多個SocketChannel。

7.註冊後返回一個 SelectionKey, 會和該Selector 關聯(集合) 進一步獲得各個 SelectionKey。

8.在經過 SelectionKey 反向獲取 SocketChannel , 方法 channel() 能夠經過 獲得的 channel , 完成業務處理。

 NIO 非阻塞網絡編程原理圖


d6a8a8d2bdfb898d114554dcfd89c000.jpeg

應用案例

1.編寫一個 NIO 入門案例,實現服務器端和客戶端之間的數據簡單通信(非阻塞)。

服務器端

package com.crazy.io.nio.buffer;
   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;
   /**    * @author shiyi    * @version 1.0    * @date 2020-7-8 22:32    */
   public class NIOServer {

       public static void main(String[] args) throws IOException {
           // 建立serverSocketChannel           ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
           // 建立selector           Selector selector = Selector.open();
           // 綁定一個端口6666.在服務器監聽           serverSocketChannel.socket().bind(new InetSocketAddress(6666));
           // 設置爲非阻塞           serverSocketChannel.configureBlocking(false);
           // 把serverSocketChannel註冊到selector 關心事件爲OP_ACCEPT           serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

           // 循環等待客戶端鏈接           while (true) {
               // 一秒沒有事件發生,沒有事件發生               if (selector.select(1000) == 0) {
                   System.out.println("等待了一秒,無鏈接");
                   continue;
               }
               // 若是返回的大於0,拿到selectionkey集合               // 若是大於0,表示已回去到關注的事件               // 經過selectionkeys反向獲取通道               Set<SelectionKey> selectionKeys = selector.selectedKeys();
               Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
               while (keyIterator.hasNext()) {
                   SelectionKey key = keyIterator.next();
                   // 根據key對應的通道發生的事件作相應處理                   // 有新的客戶端來鏈接了                   if (key.isAcceptable()) {
                       SocketChannel socketChannel = serverSocketChannel.accept();

                       // 將socketChannel設置爲非阻塞                       socketChannel.configureBlocking(false);
                       System.out.println("客戶端鏈接成功======");
                       // 註冊到selector上, 關注事件爲讀,給socketChannel關聯一個Buffer                       socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                   }
                   if (key.isReadable()) {
                       // 經過key 反向獲取對應的channel                       SocketChannel channel = (SocketChannel) key.channel();
                       // 獲取到管理的Buffer                       ByteBuffer buffer = (ByteBuffer) key.attachment();
                       channel.read(buffer);
                       System.out.println("form客戶端" + new String(buffer.array()));
                   }
                   keyIterator.remove();
               }
           }
       }
   }

客戶端

package com.crazy.io.nio.buffer;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;/** * @author shiyi * @version 1.0 * @date 2020-7-8 23:00 */public class NIOClient {

    public static void main(String[] args) throws IOException {
        // 等到通道        SocketChannel socketChannel = SocketChannel.open();
        // 設置非阻塞        socketChannel.configureBlocking(false);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        // 鏈接服務器        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                System.out.println("沒有鏈接上,能夠作其它事情");
            }
        }
        // 鏈接成功        String hello = "失憶老幺";
        ByteBuffer buffer = ByteBuffer.wrap(hello.getBytes());
        // 發送數據,將buffer數據寫到channel        socketChannel.write(buffer);
        System.in.read();
    }}

AIO

1.目前 AIO 尚未普遍應用。實際我也不懂

總結

cd9f2a9729cb69b6e7e839c77b67c42a.jpeg



原做者:失憶老幺
原文連接: BIO,NIO,AIO_失憶老幺-CSDN博客
原出處:CSDN博客
侵刪

52f6951ef063eca93fcf17dc02baa999.jpeg

相關文章
相關標籤/搜索