摘要:通道可以異步讀寫。使用的方法讀取數據創建一個讀數據緩沖區對象從通道中讀取數據使用的方法寫入數據創建一個寫數據緩沖區對象寫入數據關閉完成使用后,您必須關閉它。五提供了一種被稱為的新功能,也稱為本地矢量。功能是通道提供的并不是。
歷史回顧:
Java NIO 概覽
Java NIO 之 Buffer(緩沖區)
其他高贊文章:
面試中關于Redis的問題看這篇就夠了
一文輕松搞懂redis集群原理及搭建與使用
一 Channel(通道)介紹通常來說NIO中的所有IO都是從 Channel(通道) 開始的。
從通道進行數據讀取 :創建一個緩沖區,然后請求通道讀取數據。
從通道進行數據寫入 :創建一個緩沖區,填充數據,并要求通道寫入數據。
數據讀取和寫入操作圖示:
Java NIO Channel通道和流非常相似,主要有以下幾點區別:通道可以讀也可以寫,流一般來說是單向的(只能讀或者寫,所以之前我們用流進行IO操作的時候需要分別創建一個輸入流和一個輸出流)。
通道可以異步讀寫。
通道總是基于緩沖區Buffer來讀寫。
Java NIO中最重要的幾個Channel的實現:FileChannel: 用于文件的數據讀寫
DatagramChannel: 用于UDP的數據讀寫
SocketChannel: 用于TCP的數據讀寫,一般是客戶端實現
ServerSocketChannel: 允許我們監聽TCP鏈接請求,每個請求會創建會一個SocketChannel,一般是服務器實現
類層次結構:下面的UML圖使用Idea生成的。
使用FileChannel讀取數據到Buffer(緩沖區)以及利用Buffer(緩沖區)寫入數據到FileChannel:
package filechannel; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class FileChannelTxt { public static void main(String args[]) throws IOException { //1.創建一個RandomAccessFile(隨機訪問文件)對象, RandomAccessFile raf=new RandomAccessFile("D: iodata.txt", "rw"); //通過RandomAccessFile對象的getChannel()方法。FileChannel是抽象類。 FileChannel inChannel=raf.getChannel(); //2.創建一個讀數據緩沖區對象 ByteBuffer buf=ByteBuffer.allocate(48); //3.從通道中讀取數據 int bytesRead = inChannel.read(buf); //創建一個寫數據緩沖區對象 ByteBuffer buf2=ByteBuffer.allocate(48); //寫入數據 buf2.put("filechannel test".getBytes()); buf2.flip(); inChannel.write(buf); while (bytesRead != -1) { System.out.println("Read " + bytesRead); //Buffer有兩種模式,寫模式和讀模式。在寫模式下調用flip()之后,Buffer從寫模式變成讀模式。 buf.flip(); //如果還有未讀內容 while (buf.hasRemaining()) { System.out.print((char) buf.get()); } //清空緩存區 buf.clear(); bytesRead = inChannel.read(buf); } //關閉RandomAccessFile(隨機訪問文件)對象 raf.close(); } }
運行效果:
1. 開啟FileChannel使用之前,FileChannel必須被打開 ,但是你無法直接打開FileChannel(FileChannel是抽象類)。需要通過 InputStream , OutputStream 或 RandomAccessFile 獲取FileChannel。
我們上面的例子是通過RandomAccessFile打開FileChannel的:
//1.創建一個RandomAccessFile(隨機訪問文件)對象, RandomAccessFile raf=new RandomAccessFile("D: iodata.txt", "rw"); //通過RandomAccessFile對象的getChannel()方法。FileChannel是抽象類。 FileChannel inChannel=raf.getChannel();
2. 從FileChannel讀取數據/寫入數據從FileChannel中讀取數據/寫入數據之前首先要創建一個Buffer(緩沖區)對象,Buffer(緩沖區)對象的使用我們在上一篇文章中已經詳細說明了,如果不了解的話可以看我的上一篇關于Buffer的文章。
使用FileChannel的read()方法讀取數據:
//2.創建一個讀數據緩沖區對象 ByteBuffer buf=ByteBuffer.allocate(48); //3.從通道中讀取數據 int bytesRead = inChannel.read(buf);
使用FileChannel的write()方法寫入數據:
//創建一個寫數據緩沖區對象 ByteBuffer buf2=ByteBuffer.allocate(48); //寫入數據 buf2.put("filechannel test".getBytes()); buf2.flip(); inChannel.write(buf);
3. 關閉FileChannel
完成使用后,FileChannel您必須關閉它。
channel.close();三 SocketChannel和ServerSocketChannel的使用
利用SocketChannel和ServerSocketChannel實現客戶端與服務器端簡單通信:
SocketChannel 用于創建基于tcp協議的客戶端對象,因為SocketChannel中不存在accept()方法,所以,它不能成為一個服務端程序。通過 connect()方法 ,SocketChannel對象可以連接到其他tcp服務器程序。
客戶端:
package socketchannel; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class WebClient { public static void main(String[] args) throws IOException { //1.通過SocketChannel的open()方法創建一個SocketChannel對象 SocketChannel socketChannel = SocketChannel.open(); //2.連接到遠程服務器(連接此通道的socket) socketChannel.connect(new InetSocketAddress("127.0.0.1", 3333)); // 3.創建寫數據緩存區對象 ByteBuffer writeBuffer = ByteBuffer.allocate(128); writeBuffer.put("hello WebServer this is from WebClient".getBytes()); writeBuffer.flip(); socketChannel.write(writeBuffer); //創建讀數據緩存區對象 ByteBuffer readBuffer = ByteBuffer.allocate(128); socketChannel.read(readBuffer); //String 字符串常量,不可變;StringBuffer 字符串變量(線程安全),可變;StringBuilder 字符串變量(非線程安全),可變 StringBuilder stringBuffer=new StringBuilder(); //4.將Buffer從寫模式變為可讀模式 readBuffer.flip(); while (readBuffer.hasRemaining()) { stringBuffer.append((char) readBuffer.get()); } System.out.println("從服務端接收到的數據:"+stringBuffer); socketChannel.close(); } }
ServerSocketChannel 允許我們監聽TCP鏈接請求,通過ServerSocketChannelImpl的 accept()方法 可以創建一個SocketChannel對象用戶從客戶端讀/寫數據。
服務端:
package socketchannel; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; public class WebServer { public static void main(String args[]) throws IOException { try { //1.通過ServerSocketChannel 的open()方法創建一個ServerSocketChannel對象,open方法的作用:打開套接字通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //2.通過ServerSocketChannel綁定ip地址和port(端口號) ssc.socket().bind(new InetSocketAddress("127.0.0.1", 3333)); //通過ServerSocketChannelImpl的accept()方法創建一個SocketChannel對象用戶從客戶端讀/寫數據 SocketChannel socketChannel = ssc.accept(); //3.創建寫數據的緩存區對象 ByteBuffer writeBuffer = ByteBuffer.allocate(128); writeBuffer.put("hello WebClient this is from WebServer".getBytes()); writeBuffer.flip(); socketChannel.write(writeBuffer); //創建讀數據的緩存區對象 ByteBuffer readBuffer = ByteBuffer.allocate(128); //讀取緩存區數據 socketChannel.read(readBuffer); StringBuilder stringBuffer=new StringBuilder(); //4.將Buffer從寫模式變為可讀模式 readBuffer.flip(); while (readBuffer.hasRemaining()) { stringBuffer.append((char) readBuffer.get()); } System.out.println("從客戶端接收到的數據:"+stringBuffer); socketChannel.close(); ssc.close(); } catch (IOException e) { e.printStackTrace(); } } }
運行效果:
客戶端:
服務端:
考慮到篇幅問題,下面只給出大致步驟,不貼代碼,可以結合上述實例理解。
客戶端1.通過SocketChannel連接到遠程服務器 2.創建讀數據/寫數據緩沖區對象來讀取服務端數據或向服務端發送數據 3.關閉SocketChannel服務端
1.通過ServerSocketChannel 綁定ip地址和端口號 2.通過ServerSocketChannelImpl的accept()方法創建一個SocketChannel對象用戶從客戶端讀/寫數據 3.創建讀數據/寫數據緩沖區對象來讀取客戶端數據或向客戶端發送數據 4. 關閉SocketChannel和ServerSocketChannel四 ?DatagramChannel的使用
DataGramChannel,類似于java 網絡編程的DatagramSocket類;使用UDP進行網絡傳輸, UDP是無連接,面向數據報文段的協議,對傳輸的數據不保證安全與完整 ;和上面介紹的SocketChannel和ServerSocketChannel的使用方法類似,所以這里就簡單介紹一下如何使用。
1.獲取DataGramChannel
//1.通過DatagramChannel的open()方法創建一個DatagramChannel對象 DatagramChannel datagramChannel = DatagramChannel.open(); //綁定一個port(端口) datagramChannel.bind(new InetSocketAddress(1234));
上面代碼表示程序可以在1234端口接收數據報。
2.接收/發送消息
接收消息:
先創建一個緩存區對象,然后通過receive方法接收消息,這個方法返回一個SocketAddress對象,表示發送消息方的地址:
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); channel.receive(buf);
發送消息:
由于UDP下,服務端和客戶端通信并不需要建立連接,只需要知道對方地址即可發出消息,但是是否發送成功或者成功被接收到是沒有保證的;發送消息通過send方法發出,改方法返回一個int值,表示成功發送的字節數:
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put("datagramchannel".getBytes()); buf.flip(); int send = channel.send(buffer, new InetSocketAddress("localhost",1234));
這個例子發送一串字符:“datagramchannel”到主機名為”localhost”服務器的端口1234上。
五 Scatter / GatherChannel 提供了一種被稱為 Scatter/Gather 的新功能,也稱為本地矢量 I/O。Scatter/Gather 是指在多個緩沖區上實現一個簡單的 I/O 操作。正確使用 Scatter / Gather可以明顯提高性能。
大多數現代操作系統都支持本地矢量I/O(native vectored I/O)操作。當您在一個通道上請求一個Scatter/Gather操作時,該請求會被翻譯為適當的本地調用來直接填充或抽取緩沖區,減少或避免了緩沖區拷貝和系統調用;
Scatter/Gather應該使用直接的ByteBuffers以從本地I/O獲取最大性能優勢。
Scatter/Gather功能是通道(Channel)提供的 并不是Buffer。
Scatter: 從一個Channel讀取的信息分散到N個緩沖區中(Buufer).
Gather: 將N個Buffer里面內容按照順序發送到一個Channel.
Scattering Reads"scattering read"是把數據從單個Channel寫入到多個buffer,如下圖所示:
示例代碼:
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); ByteBuffer[] bufferArray = { header, body }; channel.read(bufferArray);
read()方法內部會負責把數據按順序寫進傳入的buffer數組內。一個buffer寫滿后,接著寫到下一個buffer中。
舉個例子,假如通道中有200個字節數據,那么header會被寫入128個字節數據,body會被寫入72個字節數據;
注意:
無論是scatter還是gather操作,都是按照buffer在數組中的順序來依次讀取或寫入的;
Gathering Writes"gathering write"把多個buffer的數據寫入到同一個channel中,下面是示意圖:
示例代碼:
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); //write data into buffers ByteBuffer[] bufferArray = { header, body }; channel.write(bufferArray);
write()方法內部會負責把數據按順序寫入到channel中。
注意:
并不是所有數據都寫入到通道,寫入的數據要根據position和limit的值來判斷,只有position和limit之間的數據才會被寫入;
舉個例子,假如以上header緩沖區中有128個字節數據,但此時position=0,limit=58;那么只有下標索引為0-57的數據才會被寫入到通道中。
六 通道之間的數據傳輸在Java NIO中如果一個channel是FileChannel類型的,那么他可以直接把數據傳輸到另一個channel。
transferFrom() :transferFrom方法把數據從通道源傳輸到FileChannel
transferTo() :transferTo方法把FileChannel數據傳輸到另一個channel
參考:官方JDK相關文檔
谷歌搜索排名第一的Java NIO教程
《Java NIO》
《Java 8編程官方參考教程(第9版)》
歡迎關注我的微信公眾號:"Java面試通關手冊"(一個有溫度的微信公眾號,期待與你共同進步~~~堅持原創,分享美文,分享各種Java學習資源):
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69385.html
摘要:抽象類有一個方法用于使通道處于阻塞模式或非阻塞模式。注意抽象類的方法是由抽象類實現的,都是直接繼承了抽象類。大家有興趣可以看看的源碼,各種抽象類和抽象類上層的抽象類。 歷史回顧: Java NIO 概覽 Java NIO 之 Buffer(緩沖區) Java NIO 之 Channel(通道) 其他高贊文章: 面試中關于Redis的問題看這篇就夠了 一文輕松搞懂redis集群原理及搭建...
摘要:從通道進行數據寫入創建一個緩沖區,填充數據,并要求通道寫入數據。三之通道主要內容通道介紹通常來說中的所有都是從通道開始的。從中選擇選擇器維護注冊過的通道的集合,并且這種注冊關系都被封裝在當中停止選擇的方法方法和方法。 由于內容比較多,我下面放的一部分是我更新在我的微信公眾號上的鏈接,微信排版比較好看,更加利于閱讀。每一篇文章下面我都把文章的主要內容給列出來了,便于大家學習與回顧。 Ja...
摘要:上篇說了最基礎的五種模型,相信大家對相關的概念應該有了一定的了解,這篇文章主要講講基于多路復用的。 上篇說了最基礎的五種IO模型,相信大家對IO相關的概念應該有了一定的了解,這篇文章主要講講基于多路復用IO的Java NIO。 背景 Java誕生至今,有好多種IO模型,從最早的Java IO到后來的Java NIO以及最新的Java AIO,每種IO模型都有它自己的特點,詳情請看我的上...
摘要:學習和掌握技術已經不是一個攻城獅的加分技能,而是一個必備技能。是雙向的,不僅可以讀取數據還能保存數據,程序不能直接讀寫通道,只與緩沖區交互為了讓大家不被高并發與大量連接處理問題所困擾,動力節點推出了高效處理模型應用教程。 大家肯定了解Java IO, 但是對于NIO一般是陌生的,而現在使用到NIO的場景越來越多,很多技術框...
摘要:線程之間的切換對于操作系統來說是昂貴的。因此,單線程可以監視多個通道中的數據。當方法返回后,線程可以處理這些事件。 一 NIO簡介 Java NIO 是 java 1.4 之后新出的一套IO接口,這里的的新是相對于原有標準的Java IO和Java Networking接口。NIO提供了一種完全不同的操作方式。 NIO中的N可以理解為Non-blocking,不單純是New。 它支持面...
閱讀 870·2021-09-02 09:55
閱讀 1495·2019-12-27 12:02
閱讀 1684·2019-08-30 14:24
閱讀 1138·2019-08-30 14:18
閱讀 2750·2019-08-29 13:57
閱讀 2193·2019-08-26 11:51
閱讀 1364·2019-08-26 10:37
閱讀 763·2019-08-23 16:09