channel用於字節緩衝區和位於通道另外一側的實體(一般是一個文件或套接字)之間有效的傳輸數據。
通道時一種途徑,經過這種途徑,能夠用最小的總開銷來訪問操做系統自己的IO服務,緩衝區則是通道內部用於發送和接受數據的斷點。java
頂層接口Channel,次級接口WritableByteChannel、ReadableByteChannel、InterruptibleChannel等。描述通道行爲的接口在java.nio.channels包中定義,具體的通道實現都是從java.nio.channels.spi中的類引伸來的。git
IO能夠分爲廣義的兩大類:File IO和Stream IO,對應File通道和Socket通道。體如今FileChannel類和三個socket通道類:SocketChannel、ServerSocketChannel和DatagramChannel。
代碼以下:github
//打開SocketChannel SocketChannel sc = SocketChannel.open( ); sc.connect (new InetSocketAddress("somehost", port)); //打開ServerSocketChannel ServerSocketChannel ssc = ServerSocketChannel.open( ); ssc.socket( ).bind (new InetSocketAddress (port)); DatagramChannel dc = DatagramChannel.open( ); //FileChannel只能經過 RandomAccessFile、FileInputStream 或 FileOutputStream 對象上調用 getChannel( )方法來獲取 RandomAccessFile raf = new RandomAccessFile ("somefile", "r"); FileChannel fc = raf.getChannel( );
利用通道,從控制檯接收輸入,並在控制檯打印出接收的輸入。代碼以下:web
public static void main(String[] args) throws IOException { //一個讀通道,一個寫通道 ReadableByteChannel source = Channels.newChannel(System.in); WritableByteChannel dest = Channels.newChannel(System.out); channelCopy(source,dest); source.close(); dest.close(); } private static void channelCopy(ReadableByteChannel source, WritableByteChannel dest) throws IOException{ ByteBuffer byteBuffer = ByteBuffer.allocate(16 * 1024); ByteBuffer flag = ByteBuffer.allocate(4); while (source.read(byteBuffer) != -1) { byteBuffer.flip(); //輸出標記 flag.put((byte)'-').put((byte)'-').put((byte)'-').put((byte) '>'); flag.flip(); dest.write(flag); dest.write(byteBuffer); flag.clear(); byteBuffer.compact(); } byteBuffer.flip(); //確保緩衝區排乾淨 while (byteBuffer.hasRemaining()) { flag.putChar('-').putChar('-').putChar('-'); flag.flip(); dest.write(byteBuffer); flag.clear(); } }
測試輸入輸出以下:spring
與緩衝區不一樣,通道不能重複利用,打開通道即表明與一個特定的IO服務的特定連接並封裝該連接的狀態,通道關閉時,鏈接丟失,通道不在鏈接任何東西。
調用close方法時,可能致使線程暫時阻塞,關閉的通道上調用close方法不會產生任何操做,只會當即返回。能夠經過isOpen方法判斷通道狀態。
若是一個線程被中斷,那麼這個線程訪問的通道將當即關閉,這也是爲程序健壯性而採用的一種權衡。數組
在多個緩衝區實現一個簡單的IO操做:
對於write,數據是從幾個緩衝區按順序抽取(gather)並沿着通道發送。該gather過程,比如所有緩衝區內容被鏈接起來,並在發送前存放到一個大的緩衝區。
對於read,從通道讀取的數據會被按順序散佈(scatter)到多個緩衝區,將每一個緩衝區填滿直至通道中的數據或緩衝區的空間被消耗完。
接口定義以下, 其中read和write入參時Buffer數組:瀏覽器
public interface ScatteringByteChannel extends ReadableByteChannel { public long read (ByteBuffer [] dsts) throws IOException; public long read (ByteBuffer [] dsts, int offset, int length) throws IOException; } public interface GatheringByteChannel extends WritableByteChannel { public long write(ByteBuffer[] srcs) throws IOException; public long write(ByteBuffer[] srcs, int offset, int length) throws IOException; }
具體來說FileChannel,接口以下:安全
public abstract class FileChannel extends AbstractInterruptibleChannel implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
FileChannel 對象是線程安全(thread-safe)的.
對於文件IO,最強大之處在於異步IO,它容許一個進程能夠從操做系統請求一個或多個IO操做而沒必要等待這些操做完成。springboot
public static void write(String filePath) throws Exception { /*寫文件,使用FileOutputStream,RandomAccessFile均可以。*/ /* RandomAccessFile file = new RandomAccessFile(filePath,"rw");*/ FileOutputStream file = new FileOutputStream(new File(filePath)); ByteBuffer byteBuffer = ByteBuffer.allocate(500); String str = "hello LK"; /*數據寫入緩衝區*/ byteBuffer.put(str.getBytes()); byteBuffer.flip(); FileChannel fileChannel = file.getChannel(); //將緩衝區數據寫入文件通道 fileChannel.write(byteBuffer); byteBuffer.clear(); fileChannel.close(); }
public static void read(String filePath) throws Exception { FileInputStream fileInputStream = new FileInputStream(new File(filePath)); /*一個FileChannel對象卻只能經過 在一個打開的RandomAccessFile、FileInputStream或FileOutputStream對象上調用getChannel()方法來獲取, 開發者不能直接建立一個FileChannel*/ FileChannel fileChannel = fileInputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(500); //將文件channel讀入緩衝區 fileChannel.read(byteBuffer); byteBuffer.flip(); while (byteBuffer.hasRemaining()){ System.out.print((char)byteBuffer.get()); } byteBuffer.clear(); fileChannel.close(); }
新的Socket通道類能夠運行非阻塞模式,而且是可選擇的
。藉助新的NIO類,一個或幾個線程能夠管理成百上千的活動socket鏈接,而且只有不多的性能順勢。
所有 socket 通道類(DatagramChannel、SocketChannel 和 ServerSocketChannel)都是由位於java.nio.channels.spi 包中的AbstractSelectableChannel
引伸而來。
DatagramChannel 和 SocketChannel 實現定義讀和寫功能的接口而 ServerSocketChannel 不實現。ServerSocketChannel 負責監聽傳入的鏈接和建立新的SocketChannel 對象,它自己從不傳 輸數據。服務器
啓動一個ServerSocketChannel,監聽8001端口,非阻塞模式。啓動10個SocketChannel線程向ServerSocketChannel寫數據。
ServerSocketChannel代碼以下:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); /*非阻塞*/ serverSocketChannel.configureBlocking(false); serverSocketChannel.bind(new InetSocketAddress(port)); System.out.println("ServerSocketChannel is OK,waiting @[" + LocalDateTime.now() + "]"); for (; ; ) { SocketChannel socketChannel = serverSocketChannel.accept(); if (socketChannel == null) { Thread.sleep(1000); System.out.println("ServerSocketChannel sleep 1000ms."); continue; } String connectIP = socketChannel.socket().getRemoteSocketAddress().toString(); System.out.println("客戶端已有數據到來,客戶端ip爲:" + connectIP + ", 時間爲" + LocalDateTime.now()); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); socketChannel.read(byteBuffer); byteBuffer.flip(); while (byteBuffer.hasRemaining()) { System.out.print((char) byteBuffer.get()); } socketChannel.close(); } }
啓動10個SocketCHannel代碼以下:
private static final int port = 8001; public static void main(String[] args) { for (int i=0;i<10;i++) { new SocketChannelImpl(port,i).start(); } } private static class SocketChannelImpl extends Thread { private int count = 0; private int port; public SocketChannelImpl(int port,int count){ this.port = port; this.count = count; } @Override public void run() { try { SocketChannel socketChannel = SocketChannel.open(); /*非阻塞*/ socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress(port)); for (;!socketChannel.finishConnect();) { System.out.println("connectting...."); Thread.sleep(50); } ByteBuffer byteBuffer = ByteBuffer.allocate(1024); String content = "hello, i am client--------->" + count; byteBuffer.put(content.getBytes()); byteBuffer.flip(); socketChannel.write(byteBuffer); byteBuffer.clear(); socketChannel.close(); } catch (Exception e) { e.printStackTrace(); } } }
運行結果以下:
補充下:
ServerSocketChannel監聽的是8001端口,你能夠在瀏覽器,輸入:http://localhost:8001/helloworld,你會發現你的ServerSocketChannel也是能夠收到數據了,這也web服務器處理的基礎了。
以上,瞭解了基本的通道操做,文件通道和socket通道的使用示例,我以爲點個贊,不過度=。=
以上全部代碼示例,能夠fork這裏:github
以上來自天團運營總監:坤少