快学Java NIO
Java NIO Tutorial 地址:http://tutorials.jenkov.com/java-nio/index.html
Java NIO系列教程譯文地址:http://ifeve.com/java-nio-all/
以下是我拜讀過程中摘抄的部分內容,并且加了一些內容、筆記,姑且叫《快學Java NIO》,方便以后再翻閱學習
附上一個Java NIO實現的demo,多人網絡聊天室
?
Java NIO 由以下幾個核心部分組成:
- Channels
- Buffers
- Selectors
基本上,所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點像流。 數據可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。
?
Channel
FileChannel 從文件中讀寫數據。
DatagramChannel 能通過UDP讀寫網絡中的數據。
SocketChannel 能通過TCP讀寫網絡中的數據。
ServerSocketChannel可以監聽新進來的TCP連接,像Web服務器那樣。對每一個新進來的連接都會創建一個SocketChannel。
下面是一個FileChannel的示例
public class FileChannelTest {public static void main(String[] args) throws IOException {RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();//涉及到的buffer的方法稍后解釋ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf);while (bytesRead != -1) {//make buffer ready for read buf.flip();while (buf.hasRemaining()) {System.out.print((char) buf.get());// read 1 byte at a time}
buf.clear();//buf.compact();也可以bytesRead = inChannel.read(buf);}aFile.close();} }
?
Buffer
為了理解Buffer的工作原理,需要熟悉它的三個屬性:
- capacity
- position
- limit
在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數據。 寫模式下,limit等于Buffer的capacity。
當切換Buffer到讀模式時, limit表示你最多能讀到多少數據。因此,當切換Buffer到讀模式時,limit會被設置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position)
?
clear方法就是讓position設回0,limit與capacity相等。
public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}?
flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,并將limit設置成之前position的值。
public final Buffer flip() {limit = position;position = 0;mark = -1;return this;}?
compact()方法將所有未讀的數據拷貝到Buffer起始處。然后將position設到最后一個未讀元素正后面。limit屬性依然像clear()方法一樣,設置成capacity?,F在Buffer準備好寫數據了,但是不會覆蓋未讀的數據。
public ByteBuffer compact() {System.arraycopy(hb, ix(position()), hb, ix(0), remaining());position(remaining());limit(capacity());discardMark();return this;}?
?Scatter/Gather
scatter/gather用于描述從Channel中讀取或者寫入到Channel的操作
分散(scatter)從Channel中讀取是指在讀操作時將讀取的數據寫入多個buffer中。因此,Channel將從Channel中讀取的數據“分散(scatter)”到多個Buffer中。
聚集(gather)寫入Channel是指在寫操作時將多個buffer的數據寫入同一個Channel,因此,Channel 將多個Buffer中的數據“聚集(gather)”后發送到Channel。
?
應用場景:例如傳輸一個由消息頭和消息體組成的消息,你可能會將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體
?
Scattering Reads在移動下一個buffer前,必須填滿當前的buffer,這也意味著它不適用于動態消息(譯者注:消息大小不固定)。
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024);ByteBuffer[] bufferArray = { header, body };channel.read(bufferArray);?
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024);//write data into buffers ByteBuffer[] bufferArray = { header, body };channel.write(bufferArray);?
FileChannel的transferFrom()方法可以將數據從源通道傳輸到FileChannel中,下面是一個簡單的例子:
RandomAccessFile fromFile = new RandomAccessFile("data/fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel();RandomAccessFile toFile = new RandomAccessFile("data/toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel();long position = 0; long count = fromChannel.size();//toChannel.transferFrom(fromChannel, position, count);也可以 fromChannel.transferTo(position, count, toChannel);?
Selector
Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,并能夠知曉通道是否為諸如讀寫事件做好準備的組件。這樣,一個單獨的線程可以管理多個channel,從而管理多個網絡連接,Selector能夠處理多個通道。
?
Selector selector = Selector.open();//FileChannel不能切換到非阻塞模式,所以這邊不能使FileChannel channel.configureBlocking(false);//與Selector一起使用時,Channel必須處于非阻塞模式下 SelectionKey key = channel.register(selector, SelectionKey.OP_READ); //除了注冊讀,還可以注冊connect,accept,read,write事件 while(true) {int readyChannels = selector.select(); //阻塞到至少有一個通道就緒,還有select(long timeout)超時就不阻塞,selectNow()不阻塞,沒有就返回0,當然打斷阻塞還有wakeUp()方法,可以用另外一個線程調用這個方法,操作同一個selector對象即可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.
//SelectionKey.channel()方法返回的通道需要轉型成你要處理的類型,如ServerSocketChannel或SocketChannel等} 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 }
//Selector不會自己從已選擇鍵集中移除SelectionKey實例。必須在處理完通道時自己移除。下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中。keyIterator.remove();} }
?
現在能看到的情況是,一個請求過來,到Selector這邊,selector從注冊的通道中選擇就緒的通道,然后找到具體的通道處理這個請求。
用一個selector線程來安排所有的channel!
當然為了并發,可以用多個selector,然后不同的channel來注冊。這樣就有了反向代理的感覺,selector就是反向代理服務器上的線程!
(以上是我個人對selector的理解,若理解有誤,請指正)
?
Java NIO與IO
我應該何時使用IO,何時使用NIO呢?在本文中,我會盡量清晰地解析Java NIO和IO的差異、它們的使用場景,以及它們如何影響您的代碼設計。
?
Java NIO與IO之間主要差別
IO NIO 面向流 面向緩沖 阻塞IO 非阻塞IO 無 選擇器Java NIO的緩沖導向方法是數據讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動,這就增加了處理過程中的靈活性。NIO設計中多了buffer,傳統IO如果要這個效果,需要自行定義操作buffer。
Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。
Java NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以注冊多個通道使用一個選擇器。
?
在IO設計中,我們從InputStream或 Reader逐字節讀取數據。?readline()阻塞直到整行讀完
NIO可讓您只使用一個(或幾個)單線程管理多個通道(網絡連接或文件),但付出的代價是解析數據可能會比從一個阻塞流中讀取數據更復雜。
?
如果需要管理同時打開的成千上萬個連接,這些連接每次只是發送少量的數據,例如聊天服務器,實現NIO的服務器可能是一個優勢。
如果你需要維持許多打開的連接到其他計算機上,如P2P網絡中,使用一個單獨的線程來管理你所有出站連接,可能是一個優勢。
Java NIO: 單線程管理多個連接,如下圖
如果你有少量的連接使用非常高的帶寬,一次發送大量的數據,也許典型的IO服務器實現可能非常契合。
Java IO: 一個典型的IO服務器設計- 一個連接通過一個線程處理,如下圖
至此,基本上Java NIO的大體輪廓已經明白了,鑒于篇幅不要太長,各個具體Channel的介紹移步:快學Java NIO續篇
- FileChannel
- SocketChannel
- ServerSocketChannel
- Java NIO DatagramChannel
- Pipe
?
?
?
?
轉載于:https://www.cnblogs.com/yanghuahui/p/3683600.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的快学Java NIO的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Console.WriteLine()与
- 下一篇: uifont 字体详解