1. 程式人生 > >Java NIO之Java中的IO分類

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這次就不介紹了,我後續要單獨的拿出一整篇來介紹AI