Channel就是一個通道,用於傳輸數據,兩端分別是緩衝區和實體(文件或者套接字),通道的特色(也是NIO的特色):通道中的數據老是要先讀到一個緩衝區,或者老是要從一個緩衝區中讀入。java
1) FileChannel:從文件中讀寫數據安全
2) SocketChannel:經過TCP協議讀寫網絡中的數據服務器
3) ServerSocketChannel:在服務器端能夠監聽新進來的TCP鏈接,像WEB服務器那樣,對每個新進來的請求建立一個SocketChannel網絡
4) DatagramChannel:經過UDP協議讀寫網絡中的數據併發
上面衆多的分類,是對應了不一樣的實體,這些通道包括了文件IO、TCP和UDP網絡IO。dom
下面來看看Channel的源碼:socket
1 public interface Channel extends Closeable { 2 8 public boolean isOpen(); 9 27 public void close() throws IOException; 28 29 }
從這裏咱們能夠看到,Channel接口只提供了關閉通道和檢測通道是否打開這兩個方法,剩下方法的都是由子接口和實現類來定義提供。spa
咱們選擇其中幾個來看看這些接口的源碼:.net
1 public interface WritableByteChannel 2 extends Channel 3 { 4 5 public int write(ByteBuffer src) throws IOException; 6 7 }
public interface ReadableByteChannel extends Channel
{
public int read(ByteBuffer dst) throws IOException; }
public interface ByteChannel extends ReadableByteChannel, WritableByteChannel { }
前面我提到過:通道能夠只讀、只寫或者同時讀寫,由於Channel類能夠只實現只讀接口ReadableByteChannel或者只實現只寫接口WritableByteChannel,而咱們經常使用的Channel類FileChannel、SocketChannel、DatagramChannel是雙向通訊的, 由於實現了ByteChannel接口。線程
IO在廣義上能夠分爲:文件IO和網絡IO。文件IO對應的通道爲FileChannel,而網絡IO對應的通道則有三個:SocketChannel、ServerSoketChannel和DatagramChannel。
FileChannel對象不能直接建立,只能經過FileInputStream、OutputStream、RandomAccessFile對象的getChannel()來獲取,如:
FileInputStream fis = new FileInputStream("c:/in.txt"); FileChannel fic = fis.getChannel();
FileChannel沒法設置爲非阻塞模式,它老是運行在阻塞模式下。
1 public class NIOFileReadTest 2 { 3 public static void main(String[] args) throws IOException 4 { 5 RandomAccessFile raf = new RandomAccessFile("D:/in.txt","rw"); 6 FileChannel fis = raf.getChannel(); 7 ByteBuffer buffer = ByteBuffer.allocate(1024); 8 fis.read(buffer); 9 buffer.flip(); 10 while(buffer.hasRemaining()) 11 { 12 System.out.print((char)buffer.get()); 13 } 14 buffer.clear(); 15 fis.close(); 16 } 17 }
執行結果:
FileChannel
ByteBuffer
SelectorPicked
public class NIOFileWriteTest { public static void main(String[] args) throws Exception { FileOutputStream fos = new FileOutputStream("d:/out.txt"); FileChannel fc = fos.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(10); buffer.clear(); String str = "Channel"; buffer.put(str.getBytes()); buffer.flip(); while(buffer.hasRemaining()) { fc.write(buffer); } fc.close(); fos.close(); } }
在這裏老是要記住channel是要關閉的。
ByteBuffer中的方法我在下一章再詳細介紹,這裏只要注意這點便可:通道只能使用ByteBuffer,不論是讀仍是寫,通道都要對接緩衝區。
position();返回通道的文件位置
position(long newPosition):設置通道的文件位置
將上面讀文件的程序修改下,來觀察這幾個方法:
1 public class NIOFileReadTest 2 { 3 public static void main(String[] args) throws IOException 4 { 5 RandomAccessFile raf = new RandomAccessFile("D:/in.txt","rw"); 6 FileChannel fis = raf.getChannel(); 7 System.out.println("此通道文件的總長度:" +fis.size()); 8 //當前通道的文件位置 9 long position = fis.position(); 10 System.out.println("通道當前的位置:" + position); 11 //設置新的通道文件位置,從這個位置開始讀取 12 fis.position(position + 8); 13 ByteBuffer buffer = ByteBuffer.allocate(50); 14 fis.read(buffer); 15 buffer.flip(); 16 while(buffer.hasRemaining()) 17 { 18 System.out.print((char)buffer.get()); 19 } 20 buffer.clear(); 21 fis.close(); 22 } 23 }
執行結果:
此通道文件的總長度:33 通道當前的位置:0 nel ByteBuffer Selector
FileChannel是線程安全的,能夠多個線程在同一個實例上併發操做,可是其中有些方法(改變文件通道位置或者文件大小的方法)必須是單線程操做。
SocketChannel是一個鏈接到TCP套接字的通道,獲取的方式有兩種:
一、打開一個SocketChannel並鏈接到互聯網上某臺服務器。
二、一個新鏈接到達ServerSocketChannel時,會建立一個SocketChannel。
上面這兩種模式跟IO的Socket、ServerSocket相似,下面分別來看看客戶端和服務器端:
1、SocketChannel
從通道中讀取數據
1 public class SocketChannelTest 2 { 3 public static void main(String[] args) throws Exception 4 { 5 //獲取socket通道 6 SocketChannel sc = SocketChannel.open(); 7 //設置爲非阻塞模式 8 sc.configureBlocking(false); 9 //創建鏈接,非阻塞模式下,該方法可能在鏈接創建以前就返回了 10 sc.connect(new InetSocketAddress("wap.cmread.com",80)); 11 //判斷鏈接是否創建 12 while(!sc.finishConnect()) 13 { 14 System.out.println("鏈接未創建"); 15 Thread.sleep(5); 16 } 17 ByteBuffer buffer = ByteBuffer.allocate(48); 18 int byteRead = sc.read(buffer); 19 System.out.println(byteRead); 20 sc.close(); 21 buffer.clear(); 22 } 23 }
執行結果;
鏈接未創建
鏈接未創建
0
一、第六、7行是獲取一個socket通道,而且設置爲非阻塞模式。
二、因爲是非阻塞模式,通道在調用方法connect/read/writer這三個方法時,會出現這些狀況:鏈接未創建,connect方法就返回了;還沒有讀取任何數據時,read方法就返回;還沒有寫出任何內容時,writer就返回。
三、在12行的循環代碼中,是判斷鏈接是否創建,從執行結果來看,循環執行了兩次鏈接才創建(在循環裏線程還有休眠)。
四、因爲只是創建了鏈接,通道里面其實沒有任何的數據。
五、第18行調用read方法,因爲是非阻塞模式,因此在並未讀取任何數據的狀況下就返回0(儘管通道里面沒有數據)。
將數據寫入通道
1 public class SocketChannelTest 2 { 3 public static void main(String[] args) throws Exception 4 { 5 SocketChannel sc = SocketChannel.open(); 6 String str = "non-blocking socket channel"; 7 ByteBuffer buffer = ByteBuffer.allocate(100); 8 buffer.put(str.getBytes()); 9 buffer.flip(); 10 while(buffer.hasRemaining()) 11 { 12 sc.write(buffer); 13 } 14 sc.close(); 15 buffer.clear(); 16 } 17 }
一、SocketChannel.write()方法的調用是在一個while循環中的。Write()方法沒法保證能寫多少字節到SocketChannel。因此,咱們重複調用write()直到Buffer沒有要寫的字節爲止。
2、ServerSocketChannel
ServerSocketChannel是一個能夠監聽新進來的TCP鏈接的通道。
1 public class ServerSocketChannelTest 2 { 3 public static void main(String[] args) throws Exception 4 { 5 ServerSocketChannel ssc = ServerSocketChannel.open(); 6 ssc.socket().bind(new InetSocketAddress(80)); 7 ssc.configureBlocking(false); 8 while(true) 9 { 10 SocketChannel sc = ssc.accept(); 11 if(null != sc) 12 { 13 //do something; 14 } 15 } 16 } 17 }
一、第五、六、7行,獲取一個ServerSocketChannel,而且監聽80端口,設置爲非阻塞模式。
二、經過accept方法監聽新接入進來的鏈接,這個方法會返回一個包含新進來的鏈接的SocketChannel(服務器端的通道的獲取方式)。若是是阻塞模式,該方法會一直阻塞直到有新的鏈接進來。若是是非阻塞模式,則accept方法會馬上返回,返回值是null。
三、第11行,是由於在非阻塞模式下,須要檢查SocketChannel是否爲null。
1 ServerSocketChannel ssc = ServerSocketChannel.open(); 2 ServerSocket socket = ssc.socket(); 3 ServerSocketChannel ssc1 = socket.getChannel();
一、從這代碼片斷能夠大概看到這樣一種關係:全部socket通道(SocketChannel/ServerSocketChanne/DatagramSocketChannel)在被實例化以後,都是伴隨生成對應的socket對象,就是前面IO章節介紹的java.net類(Socket/ServerSocket/DatagramSocket)。經過通道類的socket方法來獲取。
二、java.net類(Socket/ServerSocket/DatagramSocket)如今能夠經過getChannel方法來獲取對應的通道。前提是這些socket對象不是使用傳統方式(直接實例化)建立的。不然它就沒有關聯的socket通道,調用getChannel方法返回老是null。