NIO中經常使用的通道有四個:java
Java NIO的通道相似流,但又有些不一樣:數組
既能夠從通道中讀取數據,又能夠寫數據到通道。但流的讀寫一般是單向的。緩存
通道能夠異步地讀寫。服務器
通道中的數據老是要先讀到一個Buffer,或者老是要從一個Buffer中寫入。網絡
和Channel進行交互,從Channel中讀取數據,or將數據寫入Channeldom
當向buffer寫入數據時,buffer會記錄下寫了多少數據。一旦要讀取數據,須要經過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下,能夠讀取以前寫入到buffer的全部數據異步
一旦讀完了全部的數據,就須要清空緩衝區,讓它能夠再次被寫入線程
讀寫數據的通常步驟3d
ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead =channel.read(buf)
buf.flip()
buf.get()
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); FileChannel inChannel = aFile.getChannel(); //create buffer with capacity of 48 bytes ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf); //read into buffer. while (bytesRead != -1) { buf.flip(); //make buffer ready for read while(buf.hasRemaining()){ System.out.print((char) buf.get()); // read 1 byte at a time } buf.clear(); //make buffer ready for writing bytesRead = inChannel.read(buf); } aFile.close();
capacityrest
表示容量
position
表示當前讀寫的位置,讀模式時,從該位置開始讀數據,讀完以後,移到下一個位置;寫模式時,從該位置寫入數據,寫完以後,移到下一個可寫的位置;當有寫切換到讀時,重置爲0
limit
限制,讀模式下,表示最多能夠讀多少數據;在寫模式下,Buffer的limit表示你最多能往Buffer裏寫多少數據。 寫模式下,limit等於Buffer的capacity
每個Buffer類都有一個allocate方法
ByteBuffer buf = ByteBuffer.allocate(48); CharBuffer buf = CharBuffer.allocate(1024);
從Channel寫到Buffer。
int bytesRead = inChannel.read(buf);
經過Buffer的put()方法寫到Buffer裏。
buf.put(127);
flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,並將limit設置成以前position的值
從Buffer讀取數據到Channel
int bytesWritten = inChannel.write(buf);
使用get()方法從Buffer中讀取數據。
byte aByte = buf.get()
Buffer.rewind()將position設回0,因此你能夠重讀Buffer中的全部數據。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)
buf.rewind()
scatter/gather用於描述從Channel中讀取或者寫入到Channel的操做
分散(scatter)從Channel中讀取是指在讀操做時將讀取的數據寫入多個buffer中。所以,Channel將從Channel中讀取的數據「分散(scatter)」到多個Buffer中。
彙集(gather)寫入Channel是指在寫操做時將多個buffer的數據寫入同一個Channel,所以,Channel 將多個Buffer中的數據「彙集(gather)」後發送到Channel。
從Channel中讀取數據到Buffer
使用方法,channel,讀取數據到一個 Buffer數組中,填充滿一個以後再填充下一個
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); ByteBuffer[] bufferArray = { header, body }; channel.read(bufferArray);
將多個Buffer中的數據寫入Channel
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); //write data into buffers ByteBuffer[] bufferArray = { header, body }; channel.write(bufferArray);
依次將數組中的數據寫入Channel,一個寫完再寫以下一個(注意寫入的是有效數據)
Selector(選擇器)是Java NIO中可以檢測一到多個NIO通道,並可以知曉通道是否爲諸如讀寫事件作好準備的組件。這樣,一個單獨的線程能夠管理多個channel,從而管理多個網絡鏈接
Selector selector = Selector.open();
channel.configureBlocking(false); SelectionKey key = channel.register(selector, Selectionkey.OP_READ | SelectionKey.OP_WRITE); ``` 與Selector一塊兒使用時,Channel必須處於非阻塞模式下。這意味着不能將FileChannel與Selector一塊兒使用,由於FileChannel不能切換到非阻塞模式。而套接字通道均可以 register()方法的第二個參數。這是一個「interest集合」,意思是在經過Selector監聽Channel時對什麼事件感興趣。能夠監聽四種不一樣類型的事件: - Connect : SelectionKey.OP_CONNECT - Accept : SelectionKey.OP_ACCEPT - Read : SelectionKey.OP_READ - Write : SelectionKey.OP_WRITE ### SelectionKey 通道註冊以後,返回一個 `SelectionKey` 對象,其中包含一下屬性: - interest集合 - interest集合是你所選擇的感興趣的事件集合 - 判斷是否爲Accept事件 : `boolean isInterestedInAccept = (selectionKey.interestOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;` - ready集合 - 通道已經準備就緒的操做的集合 `int readySet = selectionKey.readyOps()` - 檢測channel中什麼事件或操做已經就緒: `boolean state = selectionKey.isAcceptable();` - Channel - 獲取Channel : `Channel channel = selectionKey.channel();` - Selector - 獲取Selector : `Selector selector = selectionKey.selector();` - 附加的對象(可選) - 能夠將一個對象或者更多信息附着到SelectionKey上,這樣就能方便的識別某個給定的通道 - 能夠附加 與通道一塊兒使用的Buffer,或是包含彙集數據的某個對象 selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment(); ### 經過Selector選擇通道 > 一旦向Selector註冊了一或多個通道,就能夠調用幾個重載的select()方法。這些方法返回你所感興趣的事件(如鏈接、接受、讀或寫)已經準備就緒的那些通道 調用了select()方法,而且返回值代表有一個或更多個通道就緒了,而後能夠經過調用selector的selectedKeys()方法,訪問「已選擇鍵集(selected key set)」中的就緒通道 注意每次迭代末尾的keyIterator.remove()調用。Selector不會本身從已選擇鍵集中移除SelectionKey實例。必須在處理完通道時本身移除。下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中。 ```java Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ); while(true) { int readyChannels = selector.select(); if(readyChannels == 0) continue; Set selectedKeys = selector.selectedKeys(); Iterator keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); } }
文件通道,用於讀寫文件,阻塞模式
須要經過使用一個InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例
經過 FileInputStream 獲取 FileChannel 示意
FileInputStream fileInputStream = new FileInputStream(new File("/tmp/tags.log")); FileChannel fileChannel = (fileInputStream).getChannel(); ByteBuffer buffer = ByteBuffer.allocate(128); int size = fileChannel.read(buffer); buffer.flip(); while (buffer.hasRemaining()) { System.out.print(buffer.getChar()); } fileInputStream.close();
使用 RandomAccessFile 獲取 FileChannel 示意
RandomAccessFile randomAccessFile = new RandomAccessFile("/tmp/tags.log", "rw"); FileChannel fileChannel = randomAccessFile.getChannel(); String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(100); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { fileChannel.write(buf); } // 強制寫入文件 fileChannel.force(true); ByteBuffer buffer = ByteBuffer.allocate(100); int size = fileChannel.read(buffer); buffer.flip(); while (buffer.hasRemaining()) { byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes, 0, buffer.limit()); System.out.print(new String(bytes)); } fileChannel.close(); randomAccessFile.close();