引自:http://blog.iluckymeeting.com/2018/02/02/JavaNioOverview/
Java NIO簡介
Java 1.4開始引入了Java NIO以替換標(biāo)準(zhǔn)Java IO,標(biāo)準(zhǔn)Java IO是基于流(包括byte stream和character stream)的阻塞IO,而Java NIO是基于Channel和Buffer的非阻塞IO.
Java NIO中所有的IO操作都是基于Channel和Buffer的
Java NIO的所有IO操作都是非阻塞的,當(dāng)線程要讀取IO數(shù)據(jù)時(shí),Channel會(huì)把數(shù)據(jù)讀入Buffer,在讀入的過程中線程不用阻塞等待,可以去處理別的任務(wù),當(dāng)數(shù)據(jù)讀入完成后再回來繼續(xù)處理;同理,當(dāng)線程要寫入數(shù)據(jù)時(shí),Buffer中的數(shù)據(jù)被寫入Channel,在寫入的過程中線程不用阻塞。
Java NIO有三個(gè)核心構(gòu)件:
- Channel
- Buffer
- Selector

Java NIO中的幾個(gè)核心Channel:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel

Buffer支持全部的基本類型byte、char、double、float、int、long、short,對應(yīng)的提供了
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
一個(gè)線程要管理多個(gè)Channel連接就要依賴Selector,當(dāng)然這種情況下一般是有多個(gè)打開的連接,并且各連接要處理的IO事件都比較輕量化

Channel創(chuàng)建后會(huì)注冊到Selector上,Selector的select()方法調(diào)用后會(huì)阻塞,等待注冊的多個(gè)Channel有事件到達(dá),然后select()方法返回,線程繼續(xù)處理。
Java NIO與Java IO比較
| Java IO | Java NIO |
|---|---|
| 基于Stream | 基于Buffer |
| 阻塞IO | 非阻塞IO |
| Selector |
基于Stream Vs. 基于Buffer
Java IO是基于Stream的,數(shù)據(jù)讀取操作是由Stream中讀取一個(gè)或多個(gè)byte,每個(gè)byte只能由Stream中讀取一次,如果要重復(fù)使用讀取的數(shù)據(jù)只能是將Stream讀出的數(shù)據(jù)緩存起來。
Java NIO是基于Buffer的,數(shù)據(jù)讀取操作會(huì)將Channel中的數(shù)據(jù)讀入Buffer,之后線程可以從任意位置開始使用Buffer里的數(shù)據(jù),并且可以重復(fù)讀取Buffer里的數(shù)據(jù),方便靈活。處理Buffer里的數(shù)據(jù)有兩個(gè)注意點(diǎn):
- Buffer里的數(shù)據(jù)是否是全部數(shù)據(jù),也就是是否所有數(shù)據(jù)都已經(jīng)由Channel中讀入Buffer
- Buffer是否已被填滿,如果Buffer已被填滿的情況下再往里寫入數(shù)據(jù),則之前寫入的數(shù)據(jù)會(huì)被覆蓋
阻塞IO Vs. 非阻塞IO
Java IO基于Stream的讀寫操作是阻塞的,發(fā)起讀寫操作后線程會(huì)阻塞直到讀寫完成;Java NIO基于Channel和Buffer的讀寫操作是非阻塞的,發(fā)起讀操作時(shí),數(shù)據(jù)會(huì)由Channel讀入Buffer,發(fā)起寫操作時(shí),數(shù)據(jù)會(huì)由Buffer寫入Channel,線程不必阻塞等待。
Selector
Java NIO之所以能做到非阻塞讀寫操作,是因?yàn)橛蠸elector的存在,當(dāng)Channel連接建立時(shí)會(huì)注冊到Selector上,線程通過Selector來操作Channel完成數(shù)據(jù)讀寫,Selector阻塞在select()方法上,當(dāng)有Channel達(dá)到就緒狀態(tài)時(shí),Selector會(huì)選取這個(gè)Channel進(jìn)行讀寫操作,這個(gè)過程中線程不必阻塞,可以同時(shí)去處理別的任務(wù)。
Java IO使用 Vs. Java NIO使用
假設(shè)要傳輸以下內(nèi)容:
Name:張三
Age:34
Addr:北京市
Java IO的處理是:
InputStream input = ... ; // get the InputStream from the client socket
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String addrLine = reader.readLine();
Java IO讀操作首先會(huì)阻塞在第一個(gè)reader.readLine()操作上,直到第一行數(shù)據(jù)讀取完成,接著阻塞到第二個(gè)reader.readLine()方法上,直到第二行數(shù)據(jù)讀取完成,接著阻塞在第三個(gè)reader.readLine()方法上,直到第三行數(shù)據(jù)讀取完成。由此可見Java IO操作雖然是阻塞的,但是可以明確的知道數(shù)據(jù)讀取的進(jìn)度和內(nèi)容,數(shù)據(jù)處理邏輯相對簡單。
Java NIO的處理是:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}
當(dāng)inChannel.read(buffer)被調(diào)用時(shí),只能確定Channel中有數(shù)據(jù)讀入了Buffer,但是并不知道是否所有數(shù)據(jù)都已經(jīng)讀入了Buffer中,只好不斷檢測bufferFull()方法,直到所有Buffer被填滿.
由此可見Java NIO的讀寫操作雖然不會(huì)阻塞線程,但是數(shù)據(jù)處理邏輯相對較復(fù)雜。
綜上所述,Java NIO的非阻塞特性可以讓一個(gè)線程輕松管理多個(gè)Channel連接,但是數(shù)據(jù)處理的代價(jià)較Java IO要更大,所以如果要管理大量連接并且每個(gè)連接要處理的數(shù)據(jù)量不大,則可以選擇Java NIO;如果連接數(shù)量不多,但是每個(gè)連接占用的帶寬很大,則可以選擇Java IO。