Java NIO之Java中的IO分類


前言 

前面兩篇文章(Java NIO之理解I/O模型(一)Java NIO之理解I/O模型(二))介紹了,IO的機制,以及幾種IO模型的內容,還有涉及到的設計模式。這次要寫一些更貼近實際一些的內容了,終於要說到了Java中的各種IO了。我也是邊學邊理解,有寫的不對的地方,歡迎小伙伴們指出和補充。

Java中的IO分類

BIO

BIO是指 Blocking IO 在JDK1.0的時候就引入了,直到JDK1.4一直都是Java中唯一的IO方式。它的主要實現方式就是,一個線程執行一個請求,如果請求數據量較大線程就會一直占用着,又或者請求什么也不做,也是會占用一個線程的,這樣當客戶端請求數量變多時,服務端線程數也跟着變多,最終就會導致服務端CPU崩潰的。當然可以使用線程池來減小CPU的壓力,但是畢竟線程池也不是從本質上解決問題(長鏈接,線程池大小,以及拒絕策略等等因素都要考慮)。

無論是網絡連接還是本地讀取輸出操作都是這種方式。具體的例子我就不舉了(畢竟BIO不是本次的重點)。

NIO

Java中的NIO其實就是使用的多路I/O復用模型,前面的文章已經介紹過原理了,但是在理解Java的NIO之前,還是先介紹幾個Java NIO的基礎概念:Channel(通道),Buffer(緩沖區),Selector(選擇器)。

Channel(通道)

Channel可以理解為,互通的管道,和Java的IO中的各種Stream(InputStream、OutputStream等等)一個等級,只不過Channel是雙向的,而Stream是單向的。通道的作用是將數據移入或移出道各種I/O源,即可讀又可寫。

在Java中Channel類的層次結構相當復雜,有多個接口和許多可選操作。不過,常用的也就幾個。

FileChannel

DatagramChannel

SocketChannel

ServerSocketChannel

FileChannel可以對文件進行讀和寫,DatagramChannel可以以UDP的協議來進行數據讀寫,SocketChannel以TCP的協議來對網絡兩端進行讀寫,ServerSocketChanel能夠監聽客戶端發起的TCP連接,並為每個TCP連接創建一個新的SocketChannel來進行數據讀寫。

Buffer(緩沖區)

Buffer是一個高效的數據容器,在NIO中所有的數據操作都必須經過緩沖區,這點是和BIO不同的,BIO是直接將數據寫到Stream對象中的。因為Stream對象的設計是按順序一個字節一個字節的傳送數據。雖然出於性能考慮,也可以傳遞字節數組,但是基本概念都是一個字節一個字節的傳遞數據。通道與之不同之處在於,通道會傳送緩沖區的數據塊,而且通道的基本概念就是按照一個數據塊一個數據塊的去讀和寫。所以也可以將緩沖區理解為一個字節數組,專門用來存儲以及准備好出入通道的字節。

如下圖:

 

如上圖所示,無論是客戶端發送和接收數據,還是服務端接收和相應數據,都是從緩沖區中進行數據操作的。

在Java中除了boolean外,所有的基本數據類型都有特定的Buffer子類:

ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。網絡程序幾乎只會使用ByteBuffer,但程序偶爾也會使用其他類型來取代ByteBuffer。

除了數據列表外,每個緩沖區都記錄了信息的4個關鍵部分。無論是何種類型,都有相同的方法來獲取和設置這些值:

  • 位置(position)

緩沖區中將讀取或寫入的下一個位置。這個位置從0開始計,最大值等於緩沖區的大小。可以用下面兩個方法獲取和設置。

public final int position();
public final Buffer position(int newPosition);
  • 容量(capacity)

緩沖區可以保存的最大數目。容量值在創建緩沖區時設置,此后不能改變。

可以用以下方法讀取:

public final int capacity();
  • 限度(limit)

緩沖區中可訪問數據的末尾位置。只要不改變限度,就無法讀/寫超過這個位置的數據,即使緩沖區有更大的容量也沒有用。限度可以用下面兩個放獲取和設置。

public final int limit();
public final Buffer limit(int newLimit);
  • 標記(mark)

緩沖區中客戶端指定的索引。通過調用mark()可以將標記設置為當前位置。調用reset()可以將當前位置設置為所標識的位置。

public final Buffer mark() ;
public final Buffer reset();

如果將位置設置為低於現有標記,則丟棄這個標記。

與讀取InputStream不同,讀取緩沖區實際上不會以任何方式改變緩沖區中的數據。只可能向前或向后設置位置,從而可以從緩沖區中某個特定位置開始讀取。類似的程序可以調整限度,從而控制將要讀取的數據的末尾。只有容量是固定的。

Selector(選擇器)

Selector是Java NIO中最重要的一部分,Selector的作用就是用單線程來輪詢處理注冊的Channel,一旦哪個Channel的數據准備就緒了,就可以進行處理了。

如下圖:

使用Selector,要先向Selector中注冊Channel, 然后調用它的select()方法,這個方法會一直阻塞到某個注冊的Channel中的事件准備就緒。一旦select()方法返回,線程就可以處理這些事件了,比如新的連接進入,數據接收等。

Selector類並沒有注冊新通道的方法,register()方法是在SelectableChannel類中聲明。SelectableChannel類是實現自Channel接口的,它支持將Channel注冊到Selector中。

SelectableChannel提供了兩個注冊Channel的方法:

public abstract SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException;
public final SelectionKey register(Selector sel, int ops) throws ClosedChannelException;

第一個參數代表通道要向哪個選擇器注冊。第二個參數是SelectionKey類中的一個命名常量,標識所注冊的操作。

SelectionKey定義了4個命名位常量,用戶選擇操作類型:

SelectionKey.OP_READ;
SelectionKey.OP_WRITE;
SelectionKey.OP_CONNECT;
SelectionKey.OP_ACCEPT;

當一個通道需要在同一個選擇器中關注多個操作,只需要用戶位“或”操作符組合這些常量即可。

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

第三個參數是可選的,代表鍵的附件。這個參數通常用戶存儲鏈接狀態,例如:如果要實現一個Web服務器,可能要附加一個FileInputStream或FileChannel,這個流或通道連接到服務器提供給客戶端的本地文件。

例子:

復制文件的讀寫操作,可以用來舉例說明NIO的一個大致過程。

public static void copyFileByNIO(String src,String dst) throws IOException {

        //聲明源文件和目標文件
        RandomAccessFile aFile = new RandomAccessFile(src, "rw");
        RandomAccessFile bFile = new RandomAccessFile(dst, "rw");

        //獲得傳輸通道channel
        FileChannel inChannel = aFile.getChannel();
        FileChannel outChannel = bFile.getChannel();
        //獲得容器buffer
        ByteBuffer buffer= ByteBuffer.allocate(1024);
        while(true){
            //判斷是否讀完文件
            int eof =inChannel.read(buffer);
            if(eof==-1){
                break;
            }
            //重設一下buffer的position=0,limit=position
            buffer.flip();
            //開始寫
            outChannel.write(buffer);
            //寫完要重置buffer,重設position=0,limit=capacity
            buffer.clear();
        }
        inChannel.close();
        outChannel.close();
        aFile.close();
        bFile.close();
    } 

AIO

AIO這次就不介紹了,我后續要單獨的拿出一整篇來介紹AIO。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM