上文講到Java NIO
一些基本概念。在標準的IO
中,都是基於字節流/字符流進行數據操做的,而在NIO
中則是是基於Channel
和Buffer
進行操做,其中的Channel
的雖然模擬了流的概念,實則大不相同。服務器
本文將詳細闡述NIO
中的通道Channel
的概念和具體的用法。網絡
區別 | Stream | Channel |
---|---|---|
是否支持異步 | 不支持 | 支持 |
是否支持雙向數據傳輸 | 不支持,只能單向 | 支持,既能夠從通道讀取數據,也能夠向通道寫入數據 |
是否結合Buffer使用 | 不 | 必須結合Buffer使用 |
性能 | 較低 | 較高 |
Channel
用於在字節緩衝區和位於通道另外一側的服務(一般是文件或者套接字)之間以便有效的進行數據傳輸。藉助通道,能夠用最小的總開銷來訪問操做系統自己的I/O
服務。dom
須要注意的是Channel必須結合Buffer使用,應用程序不能直接向通道中讀/寫數據,也就是緩衝區充當着應用程序和通道數據流動的轉換的角色。異步
查看Channel
的源碼。全部的接口都實現於Channel
接口,從接口上來看,全部的通道都有這兩種操做:檢查通道的開啓狀態和關閉通道。socket
1 |
public interface Channel extends Closeable { |
廣義上來講通道能夠被分爲兩類:文件I/O
和網絡I/O
,也就是文件通道和套接字通道。若是分的更細緻一點則是:工具
TCP
讀寫網絡數據;TCP
鏈接,並對每一個連接建立對應的SocketChannel
;UDP
讀寫網絡中的數據。通道既能夠是單向的也能夠是雙向的。只實現ReadableByteChannel
接口中的read()
方法或者只實現WriteableByteChannel
接口中的write()
方法的通道皆爲單向通道,同時實現ReadableByteChannel
和WriteableByteChannel
爲雙向通道,好比ByteChannel
。性能
1 |
public interface ByteChannel extends ReadableByteChannel, WritableByteChannel { |
對於Socket
通道來講,它們一直是雙向的,而對於FileChannel
來講,它一樣實現了ByteChannel
,可是經過FileInputStream
的getChannel()
獲取的FileChannel
只具備文件的只讀權限。測試
注意:調用FileChannel的write()方法會拋出了NonWriteChannelException異常。this
通道的工做模式有兩種:阻塞或非阻塞。在非阻塞模式下,調用的線程不會休眠,請求的操做會馬上返回結果;在阻塞模式下,調用的線程會產生休眠。編碼
除FileChannel
不能運行在非阻塞模式下,其他的通道均可阻塞運行也能夠以非阻塞的方式運行。
另外從SelectableChannel
引伸出的類能夠和支持有條件選擇的Selector
結合使用,進而充分利用多路複用的I/O
(Multiplexed I/O
)來提升性能。
SelectableChannel
的源碼中有如下幾個抽象方法,能夠看出支持配置兩種工做模式:
1 |
public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel { |
對於Socket
通道類來講,一般與Selector
共同使用以提升性能。須要注意的是通道不能被同時使用,一個打開的通道表明着與一個特定I/O
服務進行鏈接並封裝了該鏈接的狀態,通道一旦關閉,該鏈接便會斷開。
通道的close()
比較特殊,不管在通道時在阻塞模式下仍是非阻塞模式下,因爲close()
方法的調用而致使底層I/O
的關閉均可能會形成線程的暫時阻塞。在一個已關閉的通道上調用close()
並無任何意義,只會當即返回。
對於Socket通道來講存在直接建立新Socket通道的方法,而對於文件通道來講,升級以後的FileInputStream、FileOutputStream和RandomAccessFile提供了getChannel()方法來獲取通道。
Java NIO
中的FileChannel
是一個鏈接到文件的通道,能夠經過文件通道讀寫文件。文件通道老是阻塞式的,所以FileChannel沒法設置爲非阻塞模式。
(一). 文件寫操做:
1 |
public static void testWriteOnFileChannel() { |
(二). 文件讀操做:
1 |
public static void testReadOnFileChannel() { |
文件讀寫測試:
1 |
public static void main(String[] args) { |
(一). transferFrom()的使用
FileChannel
的transferFrom()
方法能夠將數據從源通道傳輸到FileChannel
中。下面是一個簡單的例子:
1 |
public static void testTransferFrom(){ |
(二). transferTo()的使用
transferTo()
方法將數據從FileChannel
傳輸到目標channel
中。下面是一個簡單的例子:
1 |
public static void testTransferTo() { |
Java NIO
中的ServerSocketChannel
是一個能夠監聽新進來的TCP鏈接的通道。它相似ServerSocket
,要注意的是和DatagramChannel
和SocketChannel
不一樣,ServerSocketChannel
自己不具有傳輸數據的能力,而只是負責監聽傳入的鏈接和建立新的SocketChannel
。
(一). 建立ServerSocketChannel
經過ServerSocketChannel.open()
方法來建立一個新的ServerSocketChannel
對象,該對象關聯了一個未綁定ServerSocket
的通道。經過調用該對象上的socket()
方法能夠獲取與之關聯的ServerSocket
。
1 |
ServerSocketChannel socketChannel = ServerSocketChannel.open(); |
(二). 爲ServerSocketChannel綁定監聽端口號
在JDK 1.7
以前,ServerSocketChannel
沒有bind()
方法,所以須要經過他關聯的的socket
對象的socket()
來綁定。
1 |
// JDK1.7以前 |
從JDK1.7
及之後,能夠直接經過ServerSocketChannel
的bind()
方法來綁定端口號。
1 |
// JDK1.7以後 |
(三). 設置ServerSocketChannel
的工做模式
ServerSocketChannel
底層默認採用阻塞的工做模式,它提供了一個configureBlocking()
方法,容許配置ServerSocketChannel
以非阻塞方式運行。
1 |
// 設置爲非阻塞模式 |
進一步查看configureBlocking
源碼以下:
1 |
public final SelectableChannel configureBlocking(boolean block) throws IOException { |
Javadoc解釋configureBlocking()方法用於調整底層通道的工做模式,即阻塞和非阻塞,默認是阻塞工做模式。
若是block設置爲true,直接返回當前的阻塞式的通道;若是block設置爲false,configureBlocking()方法會調用implConfigureBlocking()方法。這裏implConfigureBlocking()是由ServerSocketChannelImpl
實現,最終調用了IOUtil中的native方法configureBlocking()。
(四). 監聽新進來的鏈接
經過ServerSocketChannel.accept()
方法監聽新進來的鏈接,這裏須要根據configureBlocking()
的配置區分兩種工做模式的使用:
accept()
方法返回的時候,它返回一個包含新鏈接的SocketChannel
,不然accept()
方法會一直阻塞到有新鏈接到達。accept()
會當即返回null
,該模式下一般不會僅僅監聽一個鏈接,所以需在while
循環中調用accept()
方法.阻塞模式:
1 |
while(true) { |
非阻塞模式:
1 |
while(true) { |
(五). 關閉ServerSocketChannel
經過調用ServerSocketChannel.close()
方法來關閉ServerSocketChannel
。
1 |
serverSocketChannel.close(); |
(一). 阻塞模式
代碼示例:
1 |
public static void blockingTest() throws IOException { |
運行結果:
(二). 非阻塞模式
代碼示例:
1 |
public static void nonBlockingTest() throws IOException { |
運行結果:
Java NIO
中的SocketChannel
是一個鏈接到TCP
網絡套接字的通道,它是Socket
類的對等類。
一般SocketChannel
在客戶端向服務器發起鏈接請求,每一個SocketChannel
對象建立時都關聯一個對等的Socket
對象。一樣SocketChannel
也能夠運行在非阻塞模式下。
SocketChannel
建立的方式有兩種:
SocketChannel
並鏈接到某臺服務器上;ServerSocketChannel
時,服務端會建立一個SocketChannel
。(一). 建立SocketChannel
經過SocketChannel
的靜態方法open()
建立SocketChannel
對象。此時通道雖然打開,但並未創建鏈接。此時若是進行I/O
操做會拋出NotYetConnectedException
異常。
1 |
SocketChannel socketChannel = SocketChannel.open(); |
(二). 鏈接指定服務器
經過SocketChannel
對象的connect()
鏈接指定地址。該通道一旦鏈接,將保持鏈接狀態直到被關閉。可經過isConnected()
來肯定某個SocketChannel
當前是否已鏈接。
若是在客戶端的SocketChannel
阻塞模式下,即服務器端的ServerSocketChannel
也爲阻塞模式:
1 |
socketChannel.connect(new InetSocketAddress("127.0.0.1", 25000)); |
兩點須要注意:其一,SocketChannel須要經過configureBlocking()設置爲非阻塞模式;其二,非阻塞模式下,connect()方法調用後會異步返回,爲了肯定鏈接是否創建,須要調用finishConnect()的方法。
1 |
socketChannel.configureBlocking(false); |
(三). 從SocketChannel讀數據
利用SocketChannel
對象的read()
方法將數據從SocketChannel
讀取到Buffer
。
1 |
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); |
(四). 向SocketChannel寫數據
利用SocketChannel
對象的write()
將Buffer
的數據寫入SocketChannel
。
1 |
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); |
(五). 關閉SocketChannel
利用SocketChannel
對象的close()
方法關閉SocketChannel
。
1 |
socketChannel.close(); |
(一). 阻塞模式
代碼示例:
1 |
public static void blockingWrite() throws Exception { |
服務端打印結果:
(一). 非阻塞模式
代碼示例:
1 |
public static void nonBlockingWrite() throws Exception { |
服務端打印結果:
Java NIO
中的DatagramChannel
是一個能收發UDP
包的通道,其底層實現爲DatagramSocket + Selector
。DatagramChannel
能夠調用socket()
方法獲取對等DatagramSocket
對象。DatagramChannel
對象既能夠充當服務端(監聽者),也能夠充當客戶端(發送者)。若是須要新建立的通道負責監聽,那麼該通道必須綁定一個端口(或端口組):
數據報發送方:
1 |
public static void main(String[] args) throws Exception { |
數據報接收方:
1 |
public static void main(String[] args) throws Exception { |
先運行DatagramChannelReceiveTest
,再運行DatagramChannelSendTest
,觀察控制檯輸出:
數據報發送方:
數據報接收方:
NIO
通道提供了一個便捷的通道類Channels
,其中定義了幾種靜態的工廠方法以簡化通道和流轉換。其中經常使用的方法以下:
方法 | 返回 | 描述 |
---|---|---|
newChannel(InputStream in) | ReadableByteChannel | 返回一個將從給定的輸入流讀取數據的通道。 |
newChannel(OutputStream out) | WritableByteChannel | 返回一個將向給定的輸出流寫入數據的通道。 |
newInputStream(ReadableByteChannel ch) | InputStream | 返回一個將從給定的通道讀取字節的流。 |
newOutputStream(WritableByteChannel ch) | OutputStream | 返回一個將向給定的通道寫入字節的流。 |
newReader(ReadableByteChannel ch, CharsetDecoder dec, int minBufferCap) | Reader | 返回一個reader,它將從給定的通道讀取字節並依據提供的字符集名稱對讀取到的字節進行解碼。 |
newReader(ReadableByteChannel ch, String csName) | Reader | 返回一個reader,它將從給定的通道讀取字節並依據提供的字符集名稱將讀取到的字節解碼成字符。 |
newWriter(WritableByteChannel ch, CharsetEncoder dec, int minBufferCap) | Writer | 返回一個writer,它將使用提供的字符集名稱對字符編碼並寫到給定的通道中。 |
newWriter(WritableByteChannel ch, String csName) | Writer | 返回一個writer,它將依據提供的字符集名稱對字符編碼並寫到給定的通道中。 |
本文針對NIO
中的通道的作了詳細的介紹,對於文件通道FileChannel
,網絡通道SocketChannel
、ServerSocketChannel
和DatagramChannel
進行了實戰演示。
篇幅較長,可見NIO
提供的原生的通道API
在使用上並非太容易。