java的NIO主要有3個特性Channel、buffer、selector來保證I/O高可複用性,其中最重要的是buffer和selector操做。詳細教材查看 jakob jenkov教材:http://tutorials.jenkov.com/java-nio/index.html html
a、 channel:有點像流的管道,NIO從channel裏面獲取、發送數據。java的I/O已經從底層被NIO實現了一次,因此性能上和純粹的NIO中使用channel沒有太大的區別。
java
channel的類型主要就下面幾種:
服務器
FileChannel //文件 DatagramChannel //UDP SocketChannel //socket ServerSocketChannel //socket服務
b、 Buffer:就是在內存開闢的空間,用來臨時存放數據。這裏的buffer是以bytebuffer爲父類實現的HeapByteBuffer。
多線程
它有2個特色:一、讀寫控制、二、按着單個byte位來操做。dom
一個Buffer有3箇中有的參數:jvm
position:當前位置socket
一、寫:從當前能夠寫的地址開始(第一次從0開始),隨着寫入的增大。寫的時候最大爲capacity-1。性能
二、讀:從0開始、隨着讀開始移動增大。最大讀取到limitspa
limit:寫模式下爲capacity。當轉換爲讀模式,則limit=position(寫入的個數)線程
capacity:一個buffer的固定大小。
以下示意圖
例子:
#有一個Buffer是70字節 一、buffer.allocate(70):capacity=70、position=0、limit=70 一、寫如40個字節:capacity=70、position=40、limit=70 二、寫轉化爲讀:buffer.flip();capacity=70,position=0,limit=40 三、讀30字節:capacity=70、position=30、limit=40 四、讀轉寫: buffer.clear():全部剩餘數據都清空。capacity=70,position=0,limit=70 buffer.compact():將剩餘的全部數據複製到buffer起始。capacity=70,position=10,limit=70
一個fileChannel的例子
public static void main(String[] args) { try { RandomAccessFile aFile = new RandomAccessFile("D:/project/test/nio/1.txt", "rw"); FileChannel fileChannel = aFile.getChannel(); // buffer ByteBuffer buf = ByteBuffer.allocate(48); while (fileChannel.read(buf) != -1) { buf.flip();// 寫模式切換成讀模式 while (buf.hasRemaining()) { System.out.println(buf.get()); } buf.clear(); } aFile.close();//從寫切換到讀 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
c、buffer的分類:
buffer默認是在JVM開闢空間、而NIO比BIO在數據處理方面有一個有點:不會將數據從邏輯主存複製到JVM主存
直接從內核態的數據區讀取數據,不用在copy到jvm堆內存。
多個數據Buffer不用組合成一個,直接就程序處理了。
發送的時候,直接發送。
bytebuffer開闢空間的兩個方法。第二個方法直接在主存開闢空間、不須要在JVM中操做
//直接在JVM開闢空間 public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); } //直接在內存開闢空間 public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
普通的I/O調用都會阻塞等待,直到文件數據準備就行才能使用。而NIO則是經過一個單獨的線程不對的去詢問系統I/O數據是否準備好了。準備好後,就能夠經過存放在Selector線程中的key(處理線程的引用)來處理。經過這種主動啓動線程的方式,避免了掉多線程同時啓動,經過CPU切換切換詢問狀態的方式,節約了CPU的開銷。
a、開啓Selector
Selector selector = Selector.open();
b、註冊channel到selector
channel.configureBlocking(false);//設置非阻塞。也就是說不能和FileChannel一塊兒使用了 SelectionKey key=channel.register(selector,SelecotionKey.OP_READ);
SelectionKey:是channel在selector上的註冊標籤。當I/O事件準備好的時候,就會返回須要事件類型:
事件類型 | 註冊類型 | 類型判斷 |
監聽:accept(服務器) | SelectionKey.OP_ACCEPT | SelctionKey.isAcceptable() |
鏈接:connect(客服端、服務器) | SelectionKey.OP_CONNECT | SelctionKey.isConnectable() |
讀:read(客服端、服務器) | SelectionKey.OP_READ | SelctionKey.isReadable() |
寫:write(客服端、服務器) | SelectionKey.OP_WRITE | SelctionKey.isWritable() |
selectionKey能夠獲取channel、selector,以及添加和獲取附加對象
//這個就是獲取channel、處理數據的方式。 Channel channel = selectionKey.channel(); //這個就是獲取selector,用來處理完事件後從新註冊 Selector selector = selectionKey.selector(); //添加、獲取附加對象。 selectionKey.attch(theObject); Object attachObj = selectionKey.attachment();
(slectionKey能夠看做是一個存放channel、附加對象的容,和咱們每次註冊到selector中須要處理的事件方式。造成了一個映射關係。只要事件達成咱們就能夠繼續處理)
c、從selector中獲取事件
while(true){ //第一步:獲取事件,只有當有事件處理的時候,selecotr會返回一個大於0的值 int readyEvents = selector.select(); if(readyEvents==0) continue; //第二步:獲取事件標籤 Set<SelectionKey> keys = slector.slectionKeys(); //第三步:處理事件 Iterator keyIteraotrs = keys.interator(); while(keyIterators.hasNext()){ SelectionKey selectionKey = keyIterators.next(); //當獲取事件的時候,須要從selector刪掉。 keys.remove(selectionKey); if(selectionKey.isAcceptable()){ //do something 。。。 //註冊 }else if(selectionKey.isConnectable()){ //do something 。。。 //註冊 }else if(selectionKey.isReadable()){ //do something 。。。 //註冊 }else if(selectionKey.isWritable()){ //do something 。。。 //註冊 } } }
a、文件
try { RandomAccessFile fromFile = new RandomAccessFile("D:/project/test/nio/1.txt", "rw"); FileChannel fromFileChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("D:/project/test/nio/2.txt", "rw"); FileChannel toFileChannel = toFile.getChannel(); //不一樣channel的數據傳送 toFileChannel.transferFrom(fromFileChannel, 0, fromFileChannel.size()); // buffer // ByteBuffer buf = ByteBuffer.allocate(48); // while (fromFileChannel.read(buf) != -1) { // // buf.flip();// 寫模式切換成讀模式 // while (buf.hasRemaining()) { // System.out.println(buf.getChar()); // } // // buf.clear();// 從寫切換到讀 // } fromFile.close(); toFile.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
b、serverSocket
// 1.開啓Selector Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 二、設置channel的模式(阻塞-false、非阻塞-true) serverSocketChannel.socket().bind(new InetSocketAddress(80)); serverSocketChannel.configureBlocking(false); // 二、註冊channel到Selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int readyChannel = selector.select(); if (readyChannel == 0) continue; Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> keyIterators = keys.iterator(); while (keyIterators.hasNext()) { SelectionKey selectionKey = keyIterators.next(); keys.remove(selectionKey); SocketChannel socketChannel = null; if (selectionKey.isAcceptable()) { // 訪問事件(這裏須要獲取的serverSocketChannel) serverSocketChannel = (ServerSocketChannel) selectionKey.channel(); socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ | SelectionKey.OP_CONNECT); } else if (selectionKey.isConnectable()) { socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer buff = ByteBuffer.allocateDirect(1024); while (socketChannel.read(buff) != -1) { buff.flip(); while (buff.hasRemaining()) { System.out.println(buff.getChar()); } buff.clear(); } buff=null; socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_WRITE); } else if (selectionKey.isWritable()) { socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer buff = ByteBuffer.allocateDirect(1024); buff.put(new String("hello , i am Nio !").getBytes()); buff.flip(); socketChannel.write(buff); buff=null; socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ); } } }
c、socket
// 1.開啓Selector Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open(); // 二、設置channel的模式(阻塞-false、非阻塞-true) socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("http://localhost/", 80)); // 二、註冊channel到Selector socketChannel.register(selector, SelectionKey.OP_CONNECT); while (true) { // 返回事件 int readyChannels = selector.select(); if (readyChannels == 0) continue; // 有事件發生 // 返回的key集合(返回的是一個channel集合) Set<SelectionKey> keys = selector.selectedKeys(); // 對每個channel進行處理 Iterator<SelectionKey> keyIterators = keys.iterator(); while (keyIterators.hasNext()) { SelectionKey selectionKey = keyIterators.next(); keys.remove(selectionKey); // 鏈接發生了 if (selectionKey.isConnectable()) { // 須要將key(channel)移除、由於selector是條件觸發,若是不刪除。下次事件來了會發生問題 // 從key中獲取channel socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); // 從新註冊(前面刪除了註冊) socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { // 讀取數據 socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer buff = ByteBuffer.allocateDirect(1024); while (socketChannel.read(buff) != -1) { buff.flip(); while (buff.hasRemaining()) { System.out.println(buff.getChar()); } buff.clear(); } buff=null; socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_WRITE); } else if (selectionKey.isWritable()) { // 寫入數據 socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer buff = ByteBuffer.allocateDirect(1024); buff.put(new String("hello , i am Nio !").getBytes()); buff.blip(); socketChannel.write(buff); buff=null; socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ); } } }
d、Pipe:兩個線程之間的數據傳送。傳送用sink通道、接受用source通道
public void writeToPipeChannel() throws IOException { Pipe pipe = Pipe.open(); Pipe.SinkChannel sinkChannel = pipe.sink(); 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()) { sinkChannel.write(buf); } } public void readFromPipeChannel() throws IOException { Pipe pipe = Pipe.open(); Pipe.SourceChannel sourceChannel = pipe.source(); ByteBuffer buf = ByteBuffer.allocateDirect(48); buf.clear(); while (sourceChannel.read(buf) != -1) { buf.flip(); while (buf.hasRemaining()) { System.out.println(buf.getChar()); } buf.clear(); } }
Pipe原理圖示