Java常見面試題匯總-----------Java基礎(NIO與IO的區別)

18. NIO與IO的區別

??NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但實現方式不同,NIO主要用到的是塊,所以NIO的效率要比IO高很多。在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另一套就是網絡編程NIO。
??NIO和IO的主要區別,下表總結了Java IO和NIO之間的主要區別:

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


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

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

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

18.1、NIO和IO適用場景

??NIO是為彌補傳統IO的不足而誕生的,但是尺有所短寸有所長,NIO也有缺點,因為NIO是面向緩沖區的操作,每一次的數據處理都是對緩沖區進行的,那么就會有一個問題,在數據處理之前必須要判斷緩沖區的數據是否完整或者已經讀取完畢,如果沒有,假設數據只讀取了一部分,那么對不完整的數據處理沒有任何意義。所以每次數據處理之前都要檢測緩沖區數據。
??那么NIO和IO各適用的場景是什么呢?
??如果需要管理同時打開的成千上萬個連接,這些連接每次只是發送少量的數據,例如聊天服務器,這時候用NIO處理數據可能是個很好的選擇。
??而如果只有少量的連接,而這些連接每次要發送大量的數據,這時候傳統的IO更合適。使用哪種處理數據,需要在數據的響應等待時間和檢查緩沖區數據的時間上作比較來權衡選擇。

18.2、Java NIO 總覽

??Java NIO的三個核心基礎組件,Channels、Buffers、Selectors。其余的諸如Pipe,FileLcok都是在使用以上三個核心組件時幫助更好使用的工具類。

一、Channels和Buffers的關系

??所有的IO操作在NIO中都是以Channel開始的。一個Channel就像一個流,NIO Channel和流很近似但是也有一些不同。
??1)、你既可以讀取也可以寫入到Channel,流只能讀取或者寫入,inputStream和outputStream。
??2)、Channel可以異步地讀和寫。
??3)、channel永遠都是從一個buffer中讀或者寫入到一個buffer中去。

??

??基本的Channel實現有以下這些:
??1)、FileChannel:向文件當中讀寫數據;
??2)、DatagramChannel:通過UDP協議向網絡讀寫數據;
??3)、SocketChannel:通過TCP協議向網絡讀寫數據;
??4)、ServerSocketChannel:以一個web服務器的形式,監聽到來的TCP連接,對每個連接建立一個SocketChannel。
??涵蓋了UDP,TCP以及文件的IO操作。
??核心的buffer實現有這些:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,涵蓋了所有的基本數據類型(4類8種,除了Boolean)。也有其他的buffer如MappedByteBuffer。
一個簡單的channel例子:使用一個FileChannel將數據讀入一個buffer。

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
    System.out.println("Read " + bytesRead);
    buf.flip();

    while(buf.hasRemaining()){
        System.out.print((char) buf.get());
    }

    buf.clear();
    bytesRead = inChannel.read(buf);
}
aFile.close();

buf.flip()的意思是讀寫轉換,首先你讀入一個buffer,然后你flip,轉換讀寫,然后再從buffer中讀出。

二、NIO buffer

??NIO buffer在與NIO Channel交互時使用,數據從Channel中讀取出來放入buffer,或者從buffer中讀取出來寫入Channel。
??buffer就是一塊內存,你可以寫入數據,并且在之后讀取它。這塊內存被包裝成NIO buffer對象,它提供了一些方法來讓你更簡單地操作內存。
??buffer的基本使用,使用buffer讀寫數據基本上分為以下4部操作:
??1)、將數據寫入buffer
??2)、調用buffer.flip()
??3)、將數據從buffer中讀取出來
??4)、調用buffer.clear()或者buffer.compact()
??在寫buffer的時候,buffer會跟蹤寫入了多少數據,需要讀buffer的時候,需要調用flip()來將buffer從寫模式切換成讀模式,讀模式中只能讀取寫入的數據,而非整個buffer。
??當數據都讀完了,你需要清空buffer以供下次使用,可以有2種方法來操作:調用clear() 或者 調用compact()。
??區別:clear方法清空整個buffer,compact方法只清除你已經讀取的數據,未讀取的數據會被移到buffer的開頭,此時寫入數據會從當前數據的末尾開始。

// 創建一個容量為48的ByteBuffer
ByteBuffer buf = ByteBuffer.allocate(48);
// 從channel中讀(取數據然后寫)入buffer
int bytesRead = inChannel.read(buf); 
// 下面是讀取buffer
while (bytesRead != -1) {
    buf.flip();  // 轉換buffer為讀模式
    System.out.print((char) buf.get()); // 一次讀取一個byte
    buf.clear();  //清空buffer準備下一次寫入
}

1、buffer的Capacity,Position和Limit
??buffer有3個屬性需要熟悉以理解buffer的工作原理:
??容量(Capacity):緩沖區能夠容納的數據元素的最大數量。容量在緩沖區創建時被設定,并且永遠不能被改變。
??上界(Limit):寫模式中等價于buffer的大小,即capacity;讀模式中為當前緩沖區中一共有多少數據,即可讀的最大位置。這意味著當調用filp()方法切換成讀模式時,limit的值變成position的值,而position重新指向0。
??位置(Position):下一個要被讀或寫的元素的位置。初始化為0,buffer滿時,position最大值為capacity-1。切換成讀模式的時候,position指向0。Position會自動由相應的 get( )和 put( )函數更新。
??position和limit的值在讀/寫模式中是不一樣的。capacity的值永遠表示buffer的大小。
??下圖解釋了在讀/寫模式中Capacity,Position和Limit的意思。
??

2、創建一個buffer
??獲得一個buffer 之前必須先分配一塊內存,每個buffer類都有一個靜態方法allocate() 來做這件事。
??下例為創建一個容量為48byte的ByteBuffer:
??ByteBuffer buf = ByteBuffer.allocate(48);
??創建一個1024個字符的CharBuffer
??CharBuffer buf = CharBuffer.allocate(1024);

3、將數據寫入buffer
??寫入buffer的方法有2種:
????1)、從一個Channel中寫入buffer。
????2)、調用buffer的put()方法來自行寫入數據。
??例:
??int bytesRead = inChannel.read(buf); // 從channel讀入buffer
??buf.put(127); // 自行寫入buffer
??put方法有很多的重載形式。以供你用各種不同的方法寫入buffer中,比如從一個特定的position,或者寫入一個array。

4、flip()
??flip方法將寫模式切換成讀模式,調用flip()方法會將limit設置為position,將position設置回0。
??換句話說,position標志著寫模式中寫到哪里,切換成讀模式之后,limit標志著之前寫到哪里,也就是現在能讀到哪里。

5、從buffer中讀取數據
??有2種方法可以從buffer中讀取數據。
????1)、從buffer中讀取數據到channel中。
????2)、使用buffer的get()方法自行從buffer中讀出數據。
??例子:
??// 從buffer中讀取數據到channel中
??int bytesWritten = inChannel.write(buf);
??// 使用buffer的get()方法自行從buffer中讀出數據
??byte aByte = buf.get();
??get方法有很多的重載形式。以供你用各種不同的方法讀取buffer中的數據。例如從特定位置讀取數據,或者讀一個數組出來。

6、rewind()
??rewind()方法將position設置為0,但是不會動buffer里的數據,這樣可以從頭開始重新讀取數據,limit的值不會變,這意味著limit依舊標志著能讀多少數據。

7、clear()和compact()
??當你讀完所有的數據想要重新寫入數據時,你可以調用clear或者compact方法。
??當你調用clear()方法的時候,position被設置為0,limit被設置為capacity,換句話說,buffer的數據雖然都還在,但是buffer被初始化了,處于可以被重寫的狀態。這也就意味著如果buffer中還有沒被讀取的數據,在執行clear之后,你無法知道數據讀到哪兒了,剩下的數據還有多少。
??如果還有沒有讀完的數據,但是你想先寫數據,可以用compact()方法,這樣未讀數據會放在buffer前端,可以在未讀數據之后跟著寫新的數據。compact()會復制未讀數據到buffer前端,然后設置position為未讀數據單位后面緊跟的位置。limit還是設置為capacity,這和clear是一樣的?,F在buffer處于可以寫的狀態,但是不會覆蓋之前未讀完的數據。

8、mark()和reset()
??你可以通過調用buffer.mark()來mark一個buffer中給定的位置。然后你就可以用buffer.reset()方法來將position設置回之前mark的位置。
??例子:
??buffer.mark();
??// 調用buffer.get()方法若干次,e.g. 比如在做parsing的時候
??buffer.reset(); //set position back to mark.

9、equals() 和 compareTo()
??使用這2種方法能夠比較2個buffer。
??equals()方法:用于判斷2個buffer是否相等,2個buffer是equal的,當它們:
??1)、是同一種數據類型的buffer。
??2)、buffer中未讀取的bytes,chars等數據個數是一樣的,即(limit-position)相等,capacity不需要相等,剩余數據的索引也不需要相等。
??3)、未讀取的bytes,chars等內容是一模一樣的,即各自[position,limit-1]索引的數據要完全相等。
??如你所見,equals()方法只比較buffer的部分內容,而不是buffer中所有的數據,事實上,它只比較buffer中剩余的元素是否一樣。

compareTo()
??compareTo()方法:比較兩個buffer的剩余元素(字節,字符等),用于例如: 排序。
??在下列情況下,緩沖區被認為比另一個緩沖區“小”:
??比較是針對每個緩沖區你剩余數據(從 position 到 limit)進行的,與它們在 equals() 中的方式相同,直到不相等的元素被發現或者到達緩沖區的上界。如果一個緩沖區在不相等元素發現前已經被耗盡,較短的緩沖區被認為是小于較長的緩沖區。

三、NIO Selectors

??Selector允許一個線程來監視多個Channel,這在當你的應用建立了多個連接,但是每個連接吞吐量都較小的時候是可行的。例如:一個聊天服務器。圖為一個線程使用Selector處理三個Channel。


??要使用一個Selector,你要先注冊這個Selector的Channels。然后你調用Selector的select()方法。這個方法會阻塞,直到它注冊的Channels當中有一個準備好了的事件發生了。當select()方法返回的時候,線程可以處理這些事件,如新的連接的到來,數據收到了等。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容