Java NIO學習系列二:Channel

  上文總結了Java NIO中的Buffer相關知識點,本文中咱們來總結一下它的好兄弟:Channel。上文有說到,Java NIO中的Buffer通常和Channel配對使用,NIO中的全部IO都起始於一個Channel,一個Channel就至關於一個流,,能夠從Channel中讀取數據到Buffer,或者寫數據到Channel中。緩存

  Channel簡介服務器

  FileChannel網絡

  SocketChanneldom

  ServerSocketChannel異步

  DatagramChannelsocket

  總結spa

 

1. Channel簡介

  Java NIO中的Channel相似流,可是有一些不一樣:操作系統

  • Channel既能夠支持寫也能夠支持讀,而流則是單向的,只能支持寫或者讀;
  • Channel支持異步讀寫;
  • Channel通常和Buffer配套使用,從Channel中讀取數據到Buffer中,或從Buffer寫入到Channel中;

  Channel的主要實現類有以下幾種:code

  • FileChannel,能夠對文件讀/寫數據;
  • DatagramChannel,經過UDP從網絡讀/寫數據;
  • SocketChannel,經過TCP從網絡讀/寫數據;
  • ServerSocketChannel,容許你監聽TCP鏈接,對於每一個TCP鏈接都建立一個SocketChannel;

 

2. FileChannel

  Java NIO FileChannel是一類文件相連的channel,經過它能夠從文件讀取數據,或向文件寫數據。FileChannel類是Java NIO類庫提供的用於代替標準Java IO API來讀寫文件。FileChannel不能設置爲非阻塞模式,只能工做在阻塞模式下。server

2.1 打開FileChannel

  在使用FileChannel以前先要打開它,就I/O類庫中有三個類被修改了,用以產生FileChannel,這三個類是:InputStream、OutputStream、RandomAccessFile,以下是如何從RandomAccessFile獲取FileChannel:

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

2.2 從FileChannel讀取數據

  首先須要分配一個Buffer,從FileChannel讀取的數據會讀到Buffer中(是否是有點繞)。而後調用FileChannel的read()方法來讀數據,這個方法會把數據讀到Buffer中,返回的int表明讀取了多少字節,返回-1表明文件末尾。

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

2.3 往FileChannel寫數據

  經過調用FileChannel的write()方法能夠往其中寫數據,參數是一個Buffer:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); }

  這裏write()方法也沒有保證一次寫入多少數據,咱們只是不斷重複直到寫完。

2.4 關閉FileChannel

  用完FileChannel以後記得要將其關閉:

channel.close();

2.5 FileChannel位置

  調用FileChannel對象的position()方法能夠獲取其position,也能夠調用position(long pos)設置其position:

// 獲取position
long pos channel.position();
// 設置position
channel.position(pos +123);

  若是將position設置到文件末尾後面,而後嘗試讀取文件,會返回-1;

  若是將position設置到文件末尾後面,而後嘗試向文件中寫數據,則文件會自動擴展,而且從設置的position位置處開始寫數據,這會致使「file hole」,即物理文件會有間隙。

2.6 FileChannel尺寸

  size()方法返回filechannel鏈接的文件的尺寸大小:

long fileSize = channel.size(); 

2.7 截短FileChannel

  truncate()方法能夠截短文件:

channel.truncate(1024);

  如上,將文件截取爲1024字節長度。

2.8 FileChannel Force

  FileChannel.force()方法會刷新全部未寫入到磁盤的數據到磁盤上。由於操做系統會先將數據緩存到內存中,再一次性寫入磁盤,因此不能保證寫到channel中的數據是否寫入到磁盤上,因此能夠調用flush()方法強制將數據寫入磁盤。

  force()方法有一個boolean參數,表明是否要寫入文件的元數據(好比權限):

channel.force(true);

 

3. SocketChannel

  Java NIO SocketChannel用於和TCP網絡socket相連,等同於Java網絡包中的Socket。能夠經過兩種方式來建立一個SocketChannel:

  • 打開一個SocketChannel而且將其和網絡上的某臺服務器相連;
  • 當有一個鏈接到達一個ServerSocketChannel時會自動建立一個SocketChannel;

3.1 打開Socket通道

  以下示例說明了如何打開一個SocketChannel:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

3.2 關閉Socket通道

  對於這種資源類的使用,是要記得及時關閉的:

socketChannel.close();

3.3 從Socket通道讀數據

  調用read()方法能夠讀取數據:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

3.4 往Socket通道寫數據

  調用其寫方法write()能夠向其中寫數據,使用一個Buffer做爲其參數:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); }

3.5 工做在非阻塞模式下

  能夠將SocketChannel設置爲非阻塞模式,設置以後能夠異步地調用其connect()、read()和write()方法。

connect()

  對於處於非阻塞模式下的SocketChannel,調用其connect()方法以後會當即返回,即便沒有成功創建鏈接。能夠調用finishConnect()方法來獲知是否成功創建鏈接:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); while(! socketChannel.finishConnect() ){ //wait, or do something else... }

write()

  對於處於非阻塞模式的SocketChannel,調用其write()也會返回,可是可能尚未寫入數據。所以你須要在一個循環中調用它,就像前面的例子中看到的那樣。

read()

  對於處於非阻塞模式的SocketChannel,調用其read()有可能出現返回int值,可是尚未任何數據讀入到buffer中。所以須要關注返回int值,這個能夠告訴咱們有多少數據是已經讀取的。

非阻塞模式下和Selectors一塊兒工做

  非阻塞模式下的SocketChannel適合和Selector一塊兒搭配使用。經過往Selector中註冊一個或多個SocketChannel,能夠經過Selector選擇已經就緒的Channel。具體使用稍後會詳述。

 

4. ServerSocketChannel

  Java NIO ServerSocketChannel能夠監聽TCP鏈接,就像標準Java網絡庫中的ServerSocket。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999)); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }

4.1 打開ServerSocketChannel

  很簡單,調用其open()方法就能夠開啓一個ServerSocketChannel:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

4.2 關閉ServerSocketChannel

serverSocketChannel.close();

4.3 監聽鏈接

  調用其accept()方法以後能夠監聽鏈接。在一個新的鏈接到來以前,都處於阻塞狀態,一旦新的鏈接到來,accept()方法將返回一個和這個鏈接對應的SocketChannel。

  將其放在一個while循環中就能夠不斷監聽新的鏈接:

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept(); //do something with socketChannel... }

  固然實際中while循環應該使用其餘的判斷條件而不是true。

4.4 工做在非阻塞模式下

  ServerSocketChannel一樣可設置爲非阻塞模式,此時調用其accept()會當即返回,若是沒有新的鏈接可能返回null,須要對返回值是否爲空進行校驗:

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...  } }

 

5. DatagramChannel

  DatagramChannel用於發送和接收UDP包。由於UDP是一個無鏈接協議,不是像其餘channel同樣進行讀寫操做,而是經過數據包來交換數據。

5.1 打開DatagramChannel

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

  這個例子中打開了一個DatagramChannel,能夠經過端口9999接收UDP數據包。

5.2 接收數據

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

  調用DatagramChannel的receive()方法能夠將接收到的數據包中的數據複製到指定的Buffer中。若是收到的數據大於Buffer容量,默認將其拋棄。

5.3 發送數據

String newData = "New String to write to file..."
                    + System.currentTimeMillis();    
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

  在這個例子中是向「jenkov.com」服務器的80端口發送UDP數據包。由於UDP不保證數據傳輸,因此也不會知道發送的數據包是否被收到。

5.4 鏈接到指定地址

  能夠將DatagramChannel「鏈接」到一個指定的網絡地址。由於UDP協議是無鏈接的,因此經過這種方式建立鏈接並不會像基於TCP的channel那樣建立一個真實的鏈接。可是,這樣能夠「鎖定」這個DatagramChannel,使得它只能和一個指定的ip地址交換數據。

channel.connect(new InetSocketAddress("jenkov.com", 80)); 

  在這種狀況下(鎖定),也能夠像其餘Channel那樣調用read()和write()方法,只不過不可以保證數據必定可以收到。

int bytesRead = channel.read(buf); 
int bytesWritten = channel.write(buf);

 

6. 總結

  本文主要總結了Channel的相關知識,Channel是通道,和Buffer進行交互數據,能夠讀數據到Buffer中,也能夠從Buffer往Channel寫數據。Channel主要有下面幾種:

  • FileChannel
  • SocketChannel
  • ServerSocketChannel
  • DatagramChannel

  其中FileChannel是和文件交互、SocketChannel和ServerSocketChannel是基於TCP的網絡Channel,DatagramChannel是基於UDP的網絡Channel。

相關文章
相關標籤/搜索