目錄:
Java NIO 學習筆記(一)----概述,Channel/Buffer
Java NIO 學習筆記(二)----彙集和分散,通道到通道
Java NIO 學習筆記(三)----Selector
Java NIO 學習筆記(四)----文件通道和網絡通道
Java NIO 學習筆記(五)----路徑、文件和管道 Path/Files/Pipe
Java NIO 學習筆記(六)----異步文件通道 AsynchronousFileChannel
Java NIO 學習筆記(七)----NIO/IO 的對比和總結html
FileChannel 是鏈接到文件的通道,能夠從文件中讀取數據,並將數據寫入文件,能夠替代使用標準 IO 讀寫文件的操做。java
注意 FileChannel 沒法設置爲非阻塞模式。 它始終以阻塞模式運行。編程
在使用 FileChannel 以前必須先將其打開。 沒法直接打開 FileChannel ,必須經過 InputStream,OutputStream 或 RandomAccessFile 獲取 FileChannel 。 如下是經過 RandomAccessFile 打開 FileChannel 的方法:緩存
RandomAccessFile aFile = new RandomAccessFile("D:\\test\\input.txt", "rw"); FileChannel inChannel = aFile.getChannel();
這是經過調用 read()/write() 方法之一完成的,讀和寫都是針對一個 ByteBuufer 對象的。
這是一個例子:服務器
// 這裏省略 2 個 Channel 的定義... ByteBuffer buffer = ByteBuffer.allocate(48); buffer.clear(); // 清除緩衝區,準備寫入數據 int bytesRead = inChannel.read(buffer); // 將 inChannel 的數據讀入緩衝區 buffer.flip(); // 反轉緩衝區,就是把指針放到開頭,並設置 limit 標記結尾 while(buffer.hasRemaining()) { // 只要緩衝區還有數據 outChannel.write(buffer); // 就將緩衝區數據寫入通道 }
分配緩衝區後,調用 FileChannel 對象的 read() 方法將 FileChannel 中的數據讀入 Buffer ,返回的 int 表明讀取的字節數。 若是返回 -1,則到達文件結尾。網絡
使用 write() 方法將數據寫入 FileChannel ,該方法一樣將 Buffer 做爲參數,注意在 while 循環中調用 write() 方法。 這是由於沒法保證 write() 方法寫入 FileChannel 的字節數。 所以,咱們重複調用 write() 方法,直到 Buffer 中沒有要寫入通道的字節。dom
使用 FileChannel 後,必須將其關閉:異步
channel.close();
能夠調用 position() 方法獲取 FileChannel 對象的當前位置,調用 position(long pos) 方法來設置 FileChannel 的位置,這樣就能夠在特定位置開始讀取或寫入 FileChannel 。
一個例子:socket
// 獲取當前位置 long pos = channel.position(); // 指定通道的位置,0 <= position <= limit channel.position(pos +123);
若是位置設置在文件結束後面:函數
FileChannel 對象的 size() 方法返回通道所鏈接文件的文件大小。 這是一個簡單的例子:
long fileSize = channel.size();
能夠經過調用 FileChannel 對象的 truncate() 方法截斷文件。 截斷文件時,會以給定長度將其剪切掉,即指定長度後面部分被刪除。 這是一個例子,將文件長度截斷爲 1024 字節:
channel.truncate(1024);
FileChannel 對象的 void force(boolean metaData)
方法強制將全部未寫入磁盤的數據從通道刷新到磁盤。 出於性能緣由,操做系統可能會將數據緩存在內存中,所以在調用 force() 方法以前,沒法保證寫入通道的數據實際寫入磁盤。
boolean 參數指定是否應該刷新文件元數據(權限信息等)到磁盤。
channel.force(true); channel.force(flase);
SocketChannel 是鏈接到 TCP 網絡套接字的通道,至關於 Java 網絡編程的套接字。能夠經過兩種方式建立:
如下是打開SocketChannel的方法:
SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("http://baidu.com", 80)); socketChannel.close();
InetSocketAddress 是 SocketAddress 類的子類,爲(IP地址+端口號)類型,也就是端口地址類型,可使用靜態方法 createUnresolved(String host, int port)
獲取對象,另外也能由構造函數 InetSocketAddress(InetAddress addr, int port)
建立,其中 InetAddress 對象可省略,也可用字符串代替。
// 這裏省略 2 個 Channel 的定義... ByteBuffer buffer = ByteBuffer.allocate(48); buffer.clear(); // 清除緩衝區,準備寫入數據 int bytesRead = socketChannel.read(buffer); // 將 socketChannel 的數據讀入緩衝區 buffer.flip(); // 反轉緩衝區,就是把指針放到開頭,並設置 limit 標記結尾 while(buffer.hasRemaining()) { // 只要緩衝區還有數據 channel.write(buffer);// 就將緩衝區數據寫入通道 }
能夠看到,基本和 FileChannel 的讀寫方式一致,首先分配緩衝區,而後調用 read() 方法。 此方法將數據從 SocketChannel 讀入 Buffer 。 read() 方法返回的 int 表明寫入了多少字節數據。 若是返回 -1 ,則到達流的末尾(鏈接已關閉)。調用 SocketChannel 對象的 write(Buffer buffer) 方法則能夠將 Buffer 的數據寫入 SocketChannel。
能夠將 SocketChannel 設置爲非阻塞模式,能夠在異步模式下調用 connect(),read() 和 write() 方法。
若是 SocketChannel 處於非阻塞模式調用 connect() 方法,則可能會在創建鏈接以前返回。 要肯定是否成功創建了鏈接,能夠調用 finishConnect() 方法,它當且僅當已鏈接此通道的套接字時才返回 true 。
以下所示:
socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("http://baidu.com", 80)); while(! socketChannel.finishConnect() ){ //wait, or do something else... }
在非阻塞模式下,write() 方法可能在沒有寫入任何內容的狀況下返回。 所以,須要在循環中調用 write() 方法。
while(buffer.hasRemaining()) { // 只要緩衝區還有數據 channel.write(buffer);// 就將緩衝區數據寫入通道 }
在非阻塞模式下,read() 方法可能在沒有讀取任何數據的狀況下返回。 所以,須要注意返回的 int ,它表明讀取了多少字節。
使用 Selector 時,SocketChannel 的非阻塞模式效果更好。 經過使用選擇器註冊一個或多個 SocketChannel ,能夠向選擇器詢問已準備好進行讀取,寫入的通道。後面會有更詳細的提起,這裏先不講。
ServerSocketChannel 是一個能夠偵聽傳入 TCP 鏈接的通道,就像標準 ServerSocket 同樣。 這是一個例子:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); while (true) { SocketChannel socketChannel = serverSocketChannel.accept(); // 監聽傳入的鏈接 //do something with socketChannel... } serverSocketChannel.close();
ServerSocketChannel 能夠設置爲非阻塞模式。 在非阻塞模式下,accept() 方法調用後會當即返回,若是有鏈接傳入,則返回 SocketChannel 對象,若是沒有鏈接傳入,則返回 null。 所以,必須檢查返回的 SocketChannel 是否爲空。以下:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while (true) { SocketChannel socketChannel = serverSocketChannel.accept(); // 監聽傳入的鏈接 if(socketChannel != null){ //do something with socketChannel... } }
DatagramChannel 是能夠發送和接收 UDP 數據包的通道。 因爲 UDP 是一種無鏈接的網絡協議,所以不能像在其餘通道中那樣讀取和寫入 DatagramChannel 。 而是經過發送和接收數據包的方式通訊。
DatagramChannel 是經過 receive() 方法接收數據的。 receive() 方法將接收到的數據包的內容複製到給定的 Buffer 中。 若是接收的數據包包含的數據多於緩衝區能夠包含的數據,則會悄悄丟棄多出的數據。一個示例以下:
ByteBuffer receiveBuffer = ByteBuffer.allocate(128); DatagramChannel serverChannel = DatagramChannel.open(); serverChannel.socket().bind(new InetSocketAddress(9999)); receiveBuffer.clear(); // 清除緩衝區,準備寫入數據 serverChannel.receive(receiveBuffer); receiveBuffer.flip(); // 反轉緩衝區以準備被讀取 System.out.println(new String(receiveBuffer.array(), 0, receiveBuffer.limit()));
ByteBuffer sendBuffer = ByteBuffer.allocate(128); sendBuffer.clear(); // 清除緩衝區,準備寫入數據 byte[] sendData = "string from cilent".getBytes(); sendBuffer.put(sendData); DatagramChannel clientChannel = DatagramChannel.open(); int sendSuccess = clientChannel.send(sendBuffer, new InetSocketAddress("127.0.0.1", 9999)); System.out.println("sendSuccess: " + sendSuccess); clientChannel.close();
此示例將字符串發送到 UDP 本機的 9999 端口, 因爲 UDP 不對數據傳送作出任何保證,所以不會通知對方是否收到了發送的數據包。
能夠將 DatagramChannel「鏈接」到網絡上的特定地址。 因爲 UDP 是無鏈接的,所以這種鏈接到地址的方式不會像 TCP 通道那樣建立真正的鏈接。 它只會鎖定 DatagramChannel ,讓其只能從一個特定地址發送和接收數據包。一個示例:
channel.connect(new InetSocketAddress("baidu.com", 80));
鏈接後,還可使用 read() 和 write() 方法,就像使用傳統通道同樣,只是在數據傳送方面沒有任何保證。 例子:
int bytesRead = channel.read(buf); int bytesWritten = channel.write(buf);