Java 基礎之NIO 學習詳解
一、NIO 簡介
java.nio 全稱 java non-blocking IO,是指 JDK 提供的新 API。從 JDK1.4 開始,Java 提供了一系列改進的輸入/輸出的新特性,被統(tǒng)稱為 NIO(即 New IO)。新增了許多用于處理輸入輸出的類,這些類都被放在 java.nio 包及子包下,并且對原 java.io 包中的很多類進行改寫,新增了滿足 NIO 的功能。
NIO 以塊的方式處理數據,塊 IO 的效率比流 IO 的效率高很多?!?br />
NIO 是非阻塞的,同時實現(xiàn)了 IO 多路復用。NIO 中用戶線程不會被讀寫操作阻塞住,它可以繼續(xù)干事情,所以 NIO 是可以做到用一個線程來處理多個操作的,使用它可以提供非阻塞的高伸縮性網絡。
1、NIO 三大核心
NIO 主要有三大核心:Channel(通道)
、Buffer(緩沖區(qū))、Selector(選擇器)
。
NIO 是基于 Channel 和緩沖區(qū)進行操作的,數據是從通道讀取到緩沖區(qū),或者是緩沖區(qū)寫入到通道中。Selector(選擇區(qū))用于監(jiān)聽多個通道的事件(比如:連接請求、數據到達等),使用單個線程就可以監(jiān)聽到多個客戶端通道。
(1)緩沖區(qū) Buffer
緩沖區(qū)就是用來存放具體要被操作和傳輸的數據。
緩沖區(qū)實際上是一個容器對象,更直接的說,其實就是一個數組,在 NIO 庫中,所有數據都是用緩沖區(qū)處理的。在讀取數據時,它是直接讀到緩沖區(qū)中的; 在寫入數據時,它也是寫入到緩沖區(qū)中的;任何時候訪問 NIO 中的數據,都是將它放到緩沖區(qū)中。Channel 提供從文件、網絡讀取數據的渠道,但是讀取或寫入的數據都必須經由 Buffer,如下圖所示:
在 NIO 中,所有的緩沖區(qū)類型都繼承于抽象類 Buffer,最常用的就是 ByteBuffer,對于 Java 中的基本類型,基本都有一個具體 Buffer 類型與之相對應,它們之間的繼承關系如下圖所示:
Buffer 的基本原理:
緩沖區(qū)對象本質上是一個數組,但它其實是一個特殊的數組,緩沖區(qū)對象內置了一些機制,能夠跟蹤和記錄緩沖區(qū)的狀態(tài)變化情況,如果使用 get()方法從緩沖區(qū)獲取數據或者使用 put()方法把數據寫入緩沖區(qū),都會引起緩沖區(qū)狀態(tài)的變化。 在緩沖區(qū)中,最重要的屬性有下面三個,它們一起合作完成對緩沖區(qū)內部狀態(tài)的變化跟蹤:
1)position
:指定下一個將要被寫入或者讀取的元素索引,它的值由 get()/put()方法自動更新,在新創(chuàng)建一個 Buffer 對象時,position 被初始化為 0。
2)limit
:指定還有多少數據需要取出(在從緩沖區(qū)寫入通道時),或者還有多少空間可以放入數據(在從通道讀入緩沖區(qū)時)。
3)capacity
:指定了可以存儲在緩沖區(qū)中的最大數據容量,實際上,它指定了底層數組的大小,或者至少是指定了準許我們使用的底層數組的容量。
以上三個屬性值之間有一些相對大小的關系:0 <= position <= limit <= capacity。如果我們創(chuàng)建一個新的容量大小為20 的 ByteBuffer 對象,在初始化的時候,position 設置為 0,limit 和 capacity 被設置為 10,在以后使用 ByteBuffer對象過程中,capacity 的值不會再發(fā)生變化,而其它兩個個將會隨著使用而變化。
(2)通道 Channel
通道 Channel 就是數據傳輸用的通道,作用是打開到IO設備的連接、文件或套接字。
通道是一個對象,通過它可以讀取和寫入數據,當然了所有數據都通過 Buffer 對象來處理。不會將字節(jié)直接寫入通道中,相反是將數據寫入包含一個或者多個字節(jié)的緩沖區(qū)。同樣不會直接從通道中讀取字節(jié),而是將數據從通道讀入緩沖區(qū),再從緩沖區(qū)獲取這個字節(jié)。
在 NIO 中,提供了多種通道對象,而所有的通道對象都實現(xiàn)了 Channel 接口。
FileChannel、DatagramChannel
用于 UDP 的數據讀寫;
ServerSocketChannel和SocketChannel
用于 TCP 的數據讀寫;
(3)Selector 選擇器
能夠檢測多個注冊的通道上是否有事件發(fā)生,如果有事件發(fā)生,便獲取事件然后針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接。這樣使得只有在連接真正有讀寫事件發(fā)生時,才會調用函數來進行讀寫,就大大地減少了系統(tǒng)開銷,并且不必為每個連接都創(chuàng)建一個線程,不用去維護多個線程,并且避免了多線程之間的上下文切換導致的開銷。
NIO 中實現(xiàn)非阻塞 I/O 的核心對象就是 Selector,Selector 就是注冊各種 I/O 事件的地方,而且當那些事件發(fā)生時,就是這個對象告訴我們所發(fā)生的事件,如下圖所示:
從圖中可以看出,當有讀或寫等任何注冊的事件發(fā)生時,可以從 Selector 中獲得相應的 SelectionKey,同時從 SelectionKey 中可以找到發(fā)生的事件和該事件所發(fā)生的具體的 SelectableChannel,以獲得客戶端發(fā)送過來的數據。
2、NIO 和 IO 的區(qū)別
面向目標不同
NIO 和傳統(tǒng) IO(一下簡稱IO)之間第一個最大的區(qū)別是,IO 是面向流的,NIO 是面向緩沖區(qū)的。 Java IO 面向流意味著每次從流中讀一個或多個字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,它不能前后移動流中的數據。如果需要前后移動從流中讀取的數據,需要先將它緩存到一個緩沖區(qū)。NIO 的緩沖導向方法略有不同。數據讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動。這就增加了處理過程中的靈活性。但是,NIO 還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩沖區(qū)時,不要覆蓋緩沖區(qū)里尚未處理的數據。
阻塞/非阻塞
IO 的各種流是阻塞的。這意味著,當一個線程調用 read() 或 write() 時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再干任何事情了。
NIO 是非阻塞模式,使一個線程從某通道發(fā)送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續(xù)做其他的事情。 線程通常將非阻塞 IO 的空閑時間用于在其它通道上執(zhí)行 IO 操作,所以一個單獨的線程現(xiàn)在可以管理多個輸入和輸出通道。
二、NIO 的 API
1、Selector
Selector 可以同時監(jiān)控多個 SelectableChannel 的 IO 狀況,是非阻塞 IO 的核心。
public abstract class Selector implements Closeable { protected Selector() { } // 創(chuàng)建 Selector 實例 public static Selector open() throws IOException { return SelectorProvider.provider().openSelector(); } public abstract boolean isOpen(); public abstract SelectorProvider provider(); // key set 代表了所有注冊在這個 Selector 上的 channel ,這個集合可以通過 keys() 方法拿到。 public abstract Set<SelectionKey> keys(); // Selected-key set 代表了所有通過 select() 方法監(jiān)測到可以進行 IO 操作的 channel ,這個集合可以通過 selectedKeys() 拿到。 public abstract Set<SelectionKey> selectedKeys(); public abstract int selectNow() throws IOException; // 可以設置超時的 select() 操作。 public abstract int select(long timeout) throws IOException; // 監(jiān)控所有注冊的 channel ,當其中有注冊的 IO 操作可以進行時,該函數返回,并將對應的 SelectionKey 加入 selected-key set 。 public abstract int select() throws IOException; // 使一個還未返回的 select() 操作立刻返回。 public abstract Selector wakeup(); public abstract void close() throws IOException; }
2、Buffer
Buffer 定義了一個可以線性存放 primitive type 數據的容器接口。 Buffer 主要包含了與類型( byte, char… )無關的功能。值得注意的是 Buffer 及其子類都不是線程安全的。
public abstract class Buffer { static final int SPLITERATOR_CHARACTERISTICS = Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED; // Invariants: mark <= position <= limit <= capacity private int mark = -1; // 一個臨時存放的位置下標。調用 mark() 會將 mark 設為當前的 position 的值,以后調用 reset() 會將 position 屬性設置為 mark 的值。 mark 的值總是小于等于 position 的值,如果將 position 的值設的比 mark 小,當前的 mark 值會被拋棄掉。 private int position = 0; // 讀 / 寫操作的當前下標。當使用 buffer 的相對位置進行讀 / 寫操作時,讀 / 寫會從這個下標進行,并在操作完成后, buffer 會更新下標的值。 private int limit; // 在 Buffer 上進行的讀寫操作都不能越過這個下標。當寫數據到 buffer 中時, limit 一般和 capacity 相等,當讀數據時, limit 代表 buffer 中有效數據的長度。 private int capacity; // 這個 Buffer 最多能放多少數據。 capacity 一般在 buffer 被創(chuàng)建的時候指定 long address; Buffer(int mark, int pos, int lim, int cap) { // package-private if (cap < 0) throw new IllegalArgumentException("Negative capacity: " + cap); this.capacity = cap; limit(lim); position(pos); if (mark >= 0) { if (mark > pos) throw new IllegalArgumentException("mark > position: (" + mark + " > " + pos + ")"); this.mark = mark; } } public final int capacity() { return capacity; } public final int position() { return position; } public final Buffer position(int newPosition) { if ((newPosition > limit) || (newPosition < 0)) throw new IllegalArgumentException(); position = newPosition; if (mark > position) mark = -1; return this; } public final int limit() { return limit; } public final Buffer limit(int newLimit) { if ((newLimit > capacity) || (newLimit < 0)) throw new IllegalArgumentException(); limit = newLimit; if (position > limit) position = limit; if (mark > limit) mark = -1; return this; } public final Buffer mark() { mark = position; return this; } // public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; } // 把 position 設為 0 ,把 limit 設為 capacity ,一般在把數據寫入 Buffer 前調用。 public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; } // 把 limit 設為當前 position ,把 position 設為 0 ,一般在從 Buffer 讀出數據前調用。 public final Buffer flip() { limit = position; position = 0; mark = -1; return this; } // 把 position 設為 0 , limit 不變,一般在把數據重寫入 Buffer 前調用。 public final Buffer rewind() { position = 0; mark = -1; return this; } public final int remaining() { return limit - position; } public final boolean hasRemaining() { return position < limit; } // 用來判斷一個 Buffer 是否只讀 public abstract boolean isReadOnly(); public abstract boolean hasArray(); public abstract Object array(); public abstract int arrayOffset(); public abstract boolean isDirect(); final int nextGetIndex() { // package-private if (position >= limit) throw new BufferUnderflowException(); return position++; } final int nextGetIndex(int nb) { // package-private if (limit - position < nb) throw new BufferUnderflowException(); int p = position; position += nb; return p; } final int nextPutIndex() { // package-private if (position >= limit) throw new BufferOverflowException(); return position++; } final int nextPutIndex(int nb) { // package-private if (limit - position < nb) throw new BufferOverflowException(); int p = position; position += nb; return p; } final int checkIndex(int i) { // package-private if ((i < 0) || (i >= limit)) throw new IndexOutOfBoundsException(); return i; } final int checkIndex(int i, int nb) { // package-private if ((i < 0) || (nb > limit - i)) throw new IndexOutOfBoundsException(); return i; } final int markValue() { // package-private return mark; } final void truncate() { // package-private mark = -1; position = 0; limit = 0; capacity = 0; } final void discardMark() { // package-private mark = -1; } static void checkBounds(int off, int len, int size) { // package-private if ((off | len | (off + len) | (size - (off + len))) < 0) throw new IndexOutOfBoundsException(); } }
Buffer子類(以下以ByteBuffer為例子)通用方法:
1)ByteBuffer(int N) :構造方法,設定緩沖區(qū)大小為N個byte大小的空間 ;
2)byte[] get():讀取buffer中的所有數據;
3)void put(byte[]):數據寫入buffer【功能和從channel中讀取數據到buffer中一樣】;
4)void filp():切換模式(寫模式->讀模式);
5)void rewind():重讀buffer中的數據(position重置為0);
6)void clear():清空。重置所有指針,不刪除數據?。。╬osition=0,limit=capacity,重新供寫入);
7)void compact():半清空,保留仍未讀取的數據。(position=最后一個未讀單元之后的位置,limit=cap,重新供寫入);
8)mark():標記時刻A的當前pos【與reset()一起用】 reset():回到時刻A時標記的pos位置;
9)close():關閉并釋放channel對象。;
3、Package java.nio.channels
Channel 是一個可以進行 IO 操作的通道(比如,通過 FileChannel ,我們可以對文件進行讀寫操作)。 java.nio.channels 包含了文件系統(tǒng)和網絡通訊相關的 channel 類。這個包通過 Selector 和SelectableChannel 這兩個類,還定義了一個進行非阻塞( non-blocking ) IO 操作的 API ,這對需要高性能 IO 的應用非常重要。
(1) java.nio.channels 中 interface 的關系:
1)Channel
Channel 表現(xiàn)了一個可以進行 IO 操作的通道,該 interface 定義了以下方法:
boolean isOpen() // 該 Channel 是否是打開的。 void close() // 關閉這個 Channel ,相關的資源會被釋放。
2)ReadableByteChannel
定義了一個可從中讀取 byte 數據的 channel interface 。
int read(ByteBuffer dst) // 從 channel 中讀取 byte 數據并寫到 ByteBuffer 中。返回讀取的 byte 數。
3)WritableByteChannel
定義了一個可向其寫 byte 數據的 channel interface 。
int write(ByteBuffer src) // 從 ByteBuffer 中讀取 byte 數據并寫到 channel 中。返回寫出的 byte 數。
4)ByteChannel
ByteChannel 并沒有定義新的方法,它的作用只是把 ReadableByteChannel 和 WritableByteChannel 合并在一起。
5)ScatteringByteChannel
繼承了 ReadableByteChannel 并提供了同時往幾個 ByteBuffer 中寫數據的能力。
6)GatheringByteChannel
繼承了 WritableByteChannel 并提供了同時從幾個 ByteBuffer 中讀數據的能力。
7)InterruptibleChannel
用來表現(xiàn)一個可以被異步關閉的 Channel 。
(2)java.nio.channels 中類的關系:
1)非阻塞 IO 的支持可以算是 NIO API 中最重要的功能,非阻塞 IO 允許應用程序同時監(jiān)控多個 channel 以提高性能,這一功能是通過 Selector , SelectableChannel 和 SelectionKey 這 3 個類來實現(xiàn)的。
2)SelectableChannel 抽象類是所有支持非阻塞 IO 操作的 channel (如 DatagramChannel 、 SocketChannel )的父類。 SelectableChannel 可以注冊到一個或多個 Selector 上以進行非阻塞 IO 操作。
3)SelectableChannel 可以是 blocking 和 non-blocking 模式(所有 channel 創(chuàng)建的時候都是 blocking 模式),只有 non-blocking 的 SelectableChannel 才可以參與非阻塞 IO 操作。
4)Selector 這個類通過 select() 函數,給應用程序提供了一個可以同時監(jiān)控多個IO channel 的方法。
5)Channel 的相關實現(xiàn)類:FileChannel、SocketChannel與ServerSocketChannel、DatagramChannel,分別對應:“文件操作通道”、“TCP通信操作通道”、“UDP通信操作通道”。這幾個實現(xiàn)類中,除了 FileChannel 不能進入非阻塞狀態(tài),其他實現(xiàn)類都可以進入非阻塞狀態(tài)。
(3)SelectableChannel 接口
public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel { protected SelectableChannel() { } public abstract SelectorProvider provider(); // 返回一個 bit mask ,表示這個 channel 上支持的 IO 操作。當前在 SelectionKey 中,用靜態(tài)常量定義了 4 種 IO 操作的 bit 值: OP_ACCEPT , OP_CONNECT , OP_READ 和 OP_WRITE 。 public abstract int validOps(); // 該 channel 是否已注冊在一個或多個 Selector 上 public abstract boolean isRegistered(); // 返回該 channe 在 Selector 上的注冊關系所對應的 SelectionKey 。若無注冊關系,返回 null 。 public abstract SelectionKey keyFor(Selector sel); // 多出來的 att 參數會作為 attachment 被存放在返回的 SelectionKey 中,這在需要存放一些 session state 的時候非常有用。 public abstract SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException; // 將當前 channel 注冊到一個 Selector 上并返回對應的 SelectionKey 。在這以后,通過調用 Selector 的 select() 函數就可以監(jiān)控這個 channel 。 ops 這個參數是一個 bit mask ,代表了需要監(jiān)控的 IO 操作。 public final SelectionKey register(Selector sel, int ops) throws ClosedChannelException { return register(sel, ops, null); } // 設置 blocking 模式 public abstract SelectableChannel configureBlocking(boolean block) throws IOException; // 返回是否為 blocking 模式 public abstract boolean isBlocking(); public abstract Object blockingLock(); }
(4)Channel 通用方法:
int read(Buffer):
將數據從 channel 讀取到 buffer 中【讀channel,寫buffer】;
int read(Buffer[]):
將數據從 channel 讀取到 buffer 數組中;
int write(Buffer):
將數據從 buffer 寫入到 channel 中【讀buffer,寫channel】;
int write(Buffer[]):
將數據從 buffer 數組寫入到 channel 中;
三、NIO 示例
1、TCP 通信 —— SocketChannel
使用 NIO 開發(fā)一個入門案例,實現(xiàn)服務器端和客戶端之間的數據通信(非阻塞)。
(1)客戶端
public class NIOClient { public static void main(String[] args) throws Exception{ //得到一個網絡通道 SocketChannel socketChannel = SocketChannel.open(); //設置非阻塞 socketChannel.configureBlocking(false); //連接網絡 InetSocketAddress address = new InetSocketAddress("localhost",8081); //判斷是否連接 if(!socketChannel.connect(address)){ while(!socketChannel.finishConnect()){ System.out.println("沒有服務端進行連接"); } } //要發(fā)送的內容 String str = "hello NIO 服務端!"; // 將要轉發(fā)的字符串內容轉換成 Byte 類型 ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); //寫入通道 socketChannel.write(byteBuffer); System.in.read(); } }
(2)服務端
public class NIOServer { public static void main(String[] args) throws Exception{ //得到通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //得到selector對象 Selector selector = Selector.open(); //設置為非阻塞 serverSocketChannel.configureBlocking(false); //設置端口 serverSocketChannel.bind(new InetSocketAddress(8081)); //注冊到selector對象上 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(true){ //監(jiān)控客戶端 if(selector.select(200)==0){ System.out.println("沒有服務端連接"); continue; } Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ //獲取所有的監(jiān)聽對象 SelectionKey selectionKey = iterator.next(); //連接客戶端 if(selectionKey.isAcceptable()){ //得到通道 SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1023)); } //讀取數據 if(selectionKey.isReadable()){ SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer buffer = (ByteBuffer) selectionKey.attachment(); socketChannel.read(buffer); System.out.printf("客戶端發(fā)來的數據:%s%n", new String(buffer.array())); } //刪除防止重復發(fā)送 iterator.remove(); } } } }
2、文件 IO —— FileChannel
(1)讀文件
public static byte[] readBytes(String fileName) { try { ///獲取對應文件的FileChannel對象 RandomAccessFile accessFile = new RandomAccessFile(fileName, "rw"); FileChannel fileChannel = accessFile.getChannel(); /// 創(chuàng)建一個緩沖區(qū)(大小為48byte) ByteBuffer byteBuffer = ByteBuffer.allocate(48); StringBuilder builder = new StringBuilder(); // 從文件向緩沖區(qū)寫入數據 int bytesRead = fileChannel.read(byteBuffer); // 若讀取到該通道數據的末尾,則返回-1 while (bytesRead != -1) { System.out.println("Read " + bytesRead); // 由向緩沖區(qū)寫入數據轉換成從緩沖區(qū)讀取數據需要調用該方法 byteBuffer.flip(); ///每次讀取完之后,輸出緩存中的內容 while (byteBuffer.hasRemaining()) { System.out.println((char) byteBuffer.get()); builder.append((char) byteBuffer.get()); } // 然后清空緩存區(qū) byteBuffer.clear(); // 重新再讀數據到緩存區(qū)中 bytesRead = fileChannel.read(byteBuffer); } accessFile.close(); return builder.toString().getBytes(); } catch (IOException e) { e.printStackTrace(); return null; } }
(2)寫文件
public static void writeBytes(String fileName, byte[] data) { try { RandomAccessFile accessFile = new RandomAccessFile(fileName, "rw"); FileChannel channel = accessFile.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(48); buffer.put(data); channel.write(buffer); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
(3)通道間內容傳輸
/** * channel 間的傳輸 * * @param sFileName 源文件 * @param dFileName 目標文件 */ public static void channelToChannel(String sFileName, String dFileName) { try { RandomAccessFile sAccess = new RandomAccessFile(sFileName, "rw"); RandomAccessFile dAccess = new RandomAccessFile(dFileName, "rw"); FileChannel sChannel = sAccess.getChannel(); FileChannel dChannel = dAccess.getChannel(); long pos = 0; long sCount = sChannel.size(); long dCount = dChannel.size(); // dChannel.transferFrom(sChannel,pos,sCount);//dChannel 必須是FileChannel sChannel.transferTo(pos, dCount, dChannel);///sChannel 是FileChannel } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
3、UDP通信 —— DatagramChannel
/** * 關于:DatagramChannel * UDP 無連接網絡協(xié)議 * 發(fā)送和接收的是數據包 */ public static void datagramChannel() { DatagramChannel datagramChannel = null; try { ///打開 datagramChannel = DatagramChannel.open(); ///連接并開始監(jiān)聽UDP 9999端口 datagramChannel.socket().bind(new InetSocketAddress(9999)); // 接收數據包(receive()方法會將接收到的數據包內容復制到指定的Buffer. 如果Buffer容不下收到的數據,多出的數據將被丟棄。 ) ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); datagramChannel.receive(buf); // 發(fā)送數據 send() String sendMsg = "要發(fā)送的數據"; ByteBuffer sendBuf = ByteBuffer.allocate(48); sendBuf.clear(); sendBuf.put(sendMsg.getBytes()); sendBuf.flip(); datagramChannel.send(sendBuf,new InetSocketAddress("xxxxx",80)); // TODO: 連接到特定的地址(鎖住DatagramChannel ,讓其只能從特定地址收發(fā)數據 因為UDP無連接,本身沒有真正的連接產出) datagramChannel.connect(new InetSocketAddress("jenkov.com", 80)); ///連接后,也可以使用Channal 的read()和write()方法,就像在用傳統(tǒng)的通道一樣。只是在數據傳送方面沒有任何保證 } catch (IOException e) { e.printStackTrace(); } finally { if (datagramChannel != null) try { datagramChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }
4、NIO 管道(Pipe)
NIO Pipe,是兩個線程之間的單向連接通道(讀下圖可知)
整體原理:ThreadA 中獲取的數據通過 SinkChannel 傳入(寫入)管道,當 ThreadB 要讀取 ThreadA 的數據,則通過管道的 SourceChannel 傳出(讀?。祿?。
Pipe 類內部有兩個成員屬性,分別是:
Pipe.SinkChannel:數據入口通道
Pipe.SourceChannel:數據出口通道
/** * 關于NIO管道(Pipe) * 定義:2個線程之間的單向數據連接 */ public static void aboutPipe(){ Pipe pipe=null; try { /// 打開管道 pipe = Pipe.open(); ///TODO: 一、 向管道寫入數據 /// 訪問Pipe.sinkChannel,向Pipe寫入數據 /// 首先,獲取Pipe.sinkChannel Pipe.SinkChannel sinkChannel = pipe.sink(); /// 然后,調用write(),開始寫入數據 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); } // TODO: 二、讀取管道中的數據 // 首先,獲取Pipe.sourceChannel Pipe.SourceChannel sourceChannel = pipe.source(); /// 讀取數據到buffer ByteBuffer buf2 = ByteBuffer.allocate(48); int bytesRead = sourceChannel.read(buf2); } catch (IOException e) { e.printStackTrace(); } }
總結
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內容!
相關文章
JAVA開發(fā)環(huán)境Vs?code配置步驟詳解
這篇文章主要為大家介紹了JAVA開發(fā)環(huán)境Vs?code配置步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04java并發(fā)編程專題(七)----(JUC)ReadWriteLock的用法
這篇文章主要介紹了java ReadWriteLock的用法,文中講解非常詳細,示例代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下2020-07-07Mybatis的@select和@SelectProvider注解方式動態(tài)SQL語句解讀
這篇文章主要介紹了Mybatis的@select和@SelectProvider注解方式動態(tài)SQL語句,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12