java IO、NIO、AIO詳解

來源:互聯網
上載者:User

標籤:數值   必須   應用   構建   figure   fail   completed   position   nio   

概述

在我們學習Java的IO流之前,我們都要瞭解幾個關鍵詞

  • 同步與非同步(synchronous/asynchronous):同步是一種可靠的有序運行機制,當我們進行同步操作時,後續的任務是等待當前調用返回,才會進行下一步;而非同步則相反,其他任務不需要等待當前調用返回,通常依靠事件、回調等機制來實現任務間次序關係
  • 阻塞與非阻塞:在進行阻塞操作時,當前線程會處於阻塞狀態,無法從事其他任務,只有當條件就緒才能繼續,比如ServerSocket新串連建立完畢,或者資料讀取、寫入操作完成;而非阻塞則是不管IO操作是否結束,直接返回,相應操作在後台繼續處理

同步和非同步概念:實際的I/O操作

同步是使用者線程發起I/O請求後需要等待或者輪詢核心I/O操作完成後才能繼續執行

非同步是使用者線程發起I/O請求後仍需要繼續執行,當核心I/O操作完成後會通知使用者線程,或者調用使用者線程註冊的回呼函數

阻塞和非阻塞的概念:發起I/O請求

阻塞是指I/O操作需要徹底完成後才能返回使用者空間

非阻塞是指I/O操作被調用後立即返回一個狀態值,無需等I/O操作徹底完成

BIO、NIO、AIO的概述

首先,傳統的 java.io包,它基於流模型實現,提供了我們最熟知的一些 IO 功能,比如 File 抽象、輸入輸出資料流等。互動方式是同步、阻塞的方式,也就是說,在讀取輸入資料流或者寫入輸出資料流時,在讀、寫動作完成之前,線程會一直阻塞在那裡,它們之間的調用是可靠的線性順序。

java.io包的好處是代碼比較簡單、直觀,缺點則是 IO 效率和擴充性存在局限性,容易成為應用效能的瓶頸。

很多時候,人們也把 java.net下面提供的部分網路功能 API,比如 Socket、ServerSocket、HttpURLConnection 也歸類到同步阻塞 IO 類庫,因為網路通訊同樣是 IO 行為。

第二,在 Java 1.4 中引入了 NIO 架構(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以構建多工、同步非阻塞 IO 程式,同時提供了更接近作業系統底層的高效能資料操作方式。

第三,在 Java 7 中,NIO 有了進一步的改進,也就是 NIO 2,引入了非同步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。非同步 IO 操作基於事件和回調機制,可以簡單理解為,應用操作直接返回,而不會阻塞在那裡,當幕後處理完成,作業系統會通知相應線程進行後續工作。

一、IO流(同步、阻塞)1、概述

IO流簡單來說就是input和output流,IO流主要是用來處理裝置之間的資料轉送,Java IO對於資料的操作都是通過流實現的,而java用於操作流的對象都在IO包中。

2、分類

按操作資料分為:位元組流(Reader、Writer)和字元流(InputStream、OutputStream)

按流向分:輸入資料流(Reader、InputStream)和輸出資料流(Writer、OutputStream)

3、字元流概述

只用來處理文本資料

資料最常見的表現形式是檔案,字元流用來操作檔案的子類一般是FileReader和FileWriter

字元流讀寫檔案注意事項:

  • 寫入檔案必須要用flush()重新整理
  • 用完流記得要關閉流
  • 使用流對象要拋出IO異常
  • 定義檔案路徑時,可以用"/"或者"\"
  • 在建立一個檔案時,如果目錄下有同名檔案將被覆蓋
  • 在讀取檔案時,必須保證該檔案已存在,否則拋出異常
字元流的緩衝區
  • 緩衝區的出現是為了提高流的操作效率而出現的
  • 需要被提高效率的流作為參數傳遞給緩衝區的建構函式
  • 在緩衝區中封裝了一個數組,存入資料後一次取出
4、位元組流概述

用來處理媒體資料

位元組流讀寫檔案注意事項:

  • 位元組流和字元流的基本操作是相同的,但是想要操作媒體流就需要用到位元組流
  • 位元組流因為操作的是位元組,所以可以用來操作媒體檔案(媒體檔案也是以位元組儲存的)
  • 輸入資料流(InputStream)、輸出資料流(OutputStream)
  • 位元組流操作可以不用重新整理流操作
  • InputStream特有方法:int available()(返迴文件中的位元組個數)

位元組流的緩衝區
位元組流緩衝區跟字元流緩衝區一樣,也是為了提高效率

5、Java Scanner類

Java 5添加了java.util.Scanner類,這是一個用於掃描輸入文本的新的公用程式

關於nextInt()、next()、nextLine()的理解

nextInt():只能讀取數值,若是格式不對,會拋出java.util.InputMismatchException異常

next():遇見第一個有效字元(非空格,非分行符號)時,開始掃描,當遇見第一個分隔字元或結束符(空格或分行符號)時,結束掃描,擷取掃描到的內容

nextLine():可以掃描到一行內容並作為字串而被捕獲到

關於hasNext()、hasNextLine()、hasNextxxx()的理解

就是為了判斷輸入行中是否還存在xxx的意思

與delimiter()有關的方法

應該是輸入內容的分隔字元設定,

二、NIO(同步、非阻塞)

NIO之所以是同步,是因為它的accept/read/write方法的核心I/O操作都會阻塞當前線程

首先,我們要先瞭解一下NIO的三個主要組成部分:Channel(通道)、Buffer(緩衝區)、Selector(選取器)

(1)Channel(通道)

Channel(通道):Channel是一個對象,可以通過它讀取和寫入資料。可以把它看做是IO中的流,不同的是:

  • Channel是雙向的,既可以讀又可以寫,而流是單向的
  • Channel可以進行非同步讀寫
  • 對Channel的讀寫必須通過buffer對象

正如上面提到的,所有資料都通過Buffer對象處理,所以,您永遠不會將位元組直接寫入到Channel中,相反,您是將資料寫入到Buffer中;同樣,您也不會從Channel中讀取位元組,而是將資料從Channel讀入Buffer,再從Buffer擷取這個位元組。

因為Channel是雙向的,所以Channel可以比流更好地反映出底層作業系統的真實情況。特別是在Unix模型中,底層作業系統通常都是雙向的。

在Java NIO中的Channel主要有如下幾種類型:

  • FileChannel:從檔案讀取資料的
  • DatagramChannel:讀寫UDP網路通訊協定資料
  • SocketChannel:讀寫TCP網路通訊協定資料
  • ServerSocketChannel:可以監聽TCP串連
(2)Buffer

Buffer是一個對象,它包含一些要寫入或者讀到Stream對象的。應用程式不能直接對 Channel 進行讀寫操作,而必須通過 Buffer 來進行,即 Channel 是通過 Buffer 來讀寫資料的。

在NIO中,所有的資料都是用Buffer處理的,它是NIO讀寫資料的中轉池。Buffer實質上是一個數組,通常是一個位元組資料,但也可以是其他類型的數組。但一個緩衝區不僅僅是一個數組,重要的是它提供了對資料的結構化訪問,而且還可以跟蹤系統的讀寫進程。

使用 Buffer 讀寫資料一般遵循以下四個步驟:

1.寫入資料到 Buffer;

2.調用 flip() 方法;

3.從 Buffer 中讀取資料;

4.調用 clear() 方法或者 compact() 方法。

當向 Buffer 寫入資料時,Buffer 會記錄下寫了多少資料。一旦要讀取資料,需要通過 flip() 方法將 Buffer 從寫入模式切換到讀模式。在讀模式下,可以讀取之前寫入到 Buffer 的所有資料。

一旦讀完了所有的資料,就需要清空緩衝區,讓它可以再次被寫入。有兩種方式能清空緩衝區:調用 clear() 或 compact() 方法。clear() 方法會清空整個緩衝區。compact() 方法只會清除已經讀過的資料。任何未讀的資料都被移到緩衝區的起始處,新寫入的資料將放到緩衝區未讀資料的後面。

Buffer主要有如下幾種:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
copyFile執行個體(NIO)

CopyFile是一個非常好的讀寫結合的例子,我們將通過CopyFile這個實力讓大家體會NIO的操作過程。CopyFile執行三個基本的操作:建立一個Buffer,然後從源檔案讀取資料到緩衝區,然後再將緩衝區寫入目標檔案。

public static void copyFileUseNIO(String src,String dst) throws IOException{//聲明源檔案和目標檔案        FileInputStream fi=new FileInputStream(new File(src));        FileOutputStream fo=new FileOutputStream(new File(dst));        //獲得傳輸通道channel        FileChannel inChannel=fi.getChannel();        FileChannel outChannel=fo.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();        fi.close();        fo.close();}   
(三)Selector(選取器對象)

首先需要瞭解一件事情就是線程環境切換開銷會在高並發時變得很明顯,這是同步阻塞方式的低擴充性劣勢。

Selector是一個對象,它可以註冊到很多個Channel上,監聽各個Channel上發生的事件,並且能夠根據事件情況決定Channel讀寫。這樣,通過一個線程管理多個Channel,就可以處理大量網路連接了。

selector優點

有了Selector,我們就可以利用一個線程來處理所有的channels。線程之間的切換對作業系統來說代價是很高的,並且每個線程也會佔用一定的系統資源。所以,對系統來說使用的線程越少越好。

1.如何建立一個Selector

Selector 就是您註冊對各種 I/O 事件興趣的地方,而且當那些事件發生時,就是這個對象告訴您所發生的事件。

Selector selector = Selector.open();
2.註冊Channel到Selector

為了能讓Channel和Selector配合使用,我們需要把Channel註冊到Selector上。通過調用 channel.register()方法來實現註冊:

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

注意,註冊的Channel 必須設定成非同步模式 才可以,否則非同步IO就無法工作,這就意味著我們不能把一個FileChannel註冊到Selector,因為FileChannel沒有非同步模式,但是網路編程中的SocketChannel是可以的。

3.關於SelectionKey

請注意對register()的調用的傳回值是一個SelectionKey。 SelectionKey 代表這個通道在此 Selector 上註冊。當某個 Selector 通知您某個傳入事件時,它是通過提供對應於該事件的 SelectionKey 來進行的。SelectionKey 還可以用於取消通道的註冊。

SelectionKey中包含如下屬性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)
(1)Interest set

就像我們在前面講到的把Channel註冊到Selector來監聽感興趣的事件,interest set就是你要選擇的感興趣的事件的集合。你可以通過SelectionKey對象來讀寫interest set:

int interestSet = selectionKey.interestOps();boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE; 

通過上面例子可以看到,我們可以通過用AND 和SelectionKey 中的常量做運算,從SelectionKey中找到我們感興趣的事件。

(2)Ready Set

ready set 是通道已經準備就緒的操作的集合。在一次選Selection之後,你應該會首先訪問這個ready set。Selection將在下一小節進行解釋。可以這樣訪問ready集合:

int readySet = selectionKey.readyOps();

可以用像檢測interest集合那樣的方法,來檢測Channel中什麼事件或操作已經就緒。但是,也可以使用以下四個方法,它們都會返回一個布爾類型:

selectionKey.isAcceptable();selectionKey.isConnectable();selectionKey.isReadable();selectionKey.isWritable();
(3)Channel 和 Selector

我們可以通過SelectionKey獲得Selector和註冊的Channel:

Channel  channel  = selectionKey.channel();Selector selector = selectionKey.selector(); 
(4)Attach一個對象

可以將一個對象或者更多資訊attach 到SelectionKey上,這樣就能方便的識別某個給定的通道。例如,可以附加 與通道一起使用的Buffer,或是包含聚集資料的某個對象。使用方法如下:

selectionKey.attach(theObject);Object attachedObj = selectionKey.attachment();

還可以在用register()方法向Selector註冊Channel的時候附加對象。如:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
4.關於SelectedKeys()

生產系統中一般會額外進行就緒狀態檢查

一旦調用了select()方法,它就會返回一個數值,表示一個或多個通道已經就緒,然後你就可以通過調用selector.selectedKeys()方法返回的SelectionKey集合來獲得就緒的Channel。請看示範方法:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

當你通過Selector註冊一個Channel時,channel.register()方法會返回一個SelectionKey對象,這個對象就代表了你註冊的Channel。這些對象可以通過selectedKeys()方法獲得。你可以通過迭代這些selected key來獲得就緒的Channel,下面是示範代碼:

Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next();if(key.isAcceptable()) {// a connection was accepted by a ServerSocketChannel.} else if (key.isConnectable()) {// a connection was established with a remote server.} else if (key.isReadable()) {// a channel is ready for reading} else if (key.isWritable()) {// a channel is ready for writing}keyIterator.remove();}

這個迴圈遍曆selected key的集合中的每個key,並對每個key做測試來判斷哪個Channel已經就緒。

請注意迴圈中最後的keyIterator.remove()方法。Selector對象並不會從自己的selected key集合中自動移除SelectionKey執行個體。我們需要在處理完一個Channel的時候自己去移除。當下一次Channel就緒的時候,Selector會再次把它添加到selected key集合中。

SelectionKey.channel()方法返回的Channel需要轉換成你具體要處理的類型,比如是ServerSocketChannel或者SocketChannel等等。

(4)NIO多工

主要步驟和元素:

  • 首先,通過 Selector.open() 建立一個 Selector,作為類似調度員的角色。

  • 然後,建立一個 ServerSocketChannel,並且向 Selector 註冊,通過指定 SelectionKey.OP_ACCEPT,告訴調度員,它關注的是新的串連請求。

  • 注意,為什麼我們要明確配置非阻塞模式呢?這是因為阻塞模式下,註冊操作是不允許的,會拋出 IllegalBlockingModeException 異常。

  • Selector 阻塞在 select 操作,當有 Channel 發生接入請求,就會被喚醒。

  • 在 具體的 方法中,通過 SocketChannel 和 Buffer 進行資料操作

IO 都是同步阻塞模式,所以需要多線程以實現多任務處理。而 NIO 則是利用了單線程輪詢事件的機制,通過高效地定位就緒的 Channel,來決定做什麼,僅僅 select 階段是阻塞的,可以有效避免大量用戶端串連時,頻繁線程切換帶來的問題,應用的擴充能力有了非常大的提高

三、NIO2(非同步、非阻塞)

AIO是非同步IO的縮寫,雖然NIO在網路操作中,提供了非阻塞的方法,但是NIO的IO行為還是同步的。對於NIO來說,我們的業務線程是在IO操作準備好時,得到通知,接著就由這個線程自行進行IO操作,IO操作本身是同步的。

但是對AIO來說,則更加進了一步,它不是在IO準備好時再通知線程,而是在IO操作已經完成後,再給線程發出通知。因此AIO是不會阻塞的,此時我們的商務邏輯將變成一個回呼函數,等待IO操作完成後,由系統自動觸發。

與NIO不同,當進行讀寫操作時,只須直接調用API的read或write方法即可。這兩種方法均為非同步,對於讀操作而言,當有流可讀取時,作業系統會將可讀的流傳入read方法的緩衝區,並通知應用程式;對於寫操作而言,當作業系統將write方法傳遞的流寫入完畢時,作業系統主動通知應用程式。 即可以理解為,read/write方法都是非同步,完成後會主動調用回呼函數。 在JDK1.7中,這部分內容被稱作NIO.2,主要在Java.nio.channels包下增加了下面四個非同步通道:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

在AIO socket編程中,服務端通道是AsynchronousServerSocketChannel,這個類提供了一個open()靜態工廠,一個bind()方法用於綁定服務端IP地址(還有連接埠號碼),另外還提供了accept()用於接收使用者串連請求。在用戶端使用的通道是AsynchronousSocketChannel,這個通道處理提供open靜態Factory 方法外,還提供了read和write方法。

在AIO編程中,發出一個事件(accept read write等)之後要指定事件處理類(回呼函數),AIO中的事件處理類是CompletionHandler<V,A>,這個介面定義了如下兩個方法,分別在非同步作業成功和失敗時被回調。

void completed(V result, A attachment);

void failed(Throwable exc, A attachment);

java IO、NIO、AIO詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.