摘要:標記,表示記錄當前的位置。直接緩沖通過方法分配的緩沖區,此緩沖區建立在物理內存中。直接在兩個空間中開辟內存空間,創建映射文件,去除了在內核地址空間和用戶地址空間中的操作,使得直接通過物理內存傳輸數據。
NIO與IO的區別
IO | NIO |
---|---|
阻塞式 | 非阻塞式、選擇器selectors |
面向流:單向流動,直接將數據從一方流向另一方 | 面向緩存:將數據放到緩存區中進行存取,經通道進行數據的傳輸 |
根據數據類型的不同,提供了對應的類型緩沖區(boolean類型除外),每一個Buffer類都是Buffer接口的一個實例。通過Buffer類.allocate()方法獲取緩沖區;對緩沖區的數據進行操作可以使用put方法和get方法。
四個核心屬性
// Invariants: mark <= position <= limit <= capacity private int mark = -1; private int position = 0; private int limit; private int capacity;
capacity:容量,表示緩沖區中最大存儲容量,一旦聲明不可更改。
limit:界限,表示限制可對緩沖區操作數據的范圍,范圍外的數據不可被操作。
position:位置,表示當前操作的數據位于緩沖區中的位置。
mark:標記,表示記錄當前position的位置。
常用方法(以ByteBuffer為例)
public static ByteBuffer allocateDirect(int capacity):分配一個直接緩沖區public static ByteBuffer allocate(int capacity):分配一個間接緩沖區
當分配一個緩沖區時,capacity=capacity,mark=-1, position=0, limit=capacity,源碼分析如下:
public static ByteBuffer allocate(int capacity) { ... return new HeapByteBuffer(capacity, capacity); } // class HeapByteBuffer extends ByteBuffer HeapByteBuffer(int cap, int lim) { // 調用ByteBuffer的構造函數傳入默認參數:mark=-1, position=0, limit=capacity super(-1, 0, lim, cap, new byte[cap], 0); }; // public abstract class ByteBuffer extends Buffer ByteBuffer(int mark, int pos, int lim, int cap, byte[] hb, int offset) { super(mark, pos, lim, cap); this.hb = hb; // final byte[] hb; this.offset = offset; // final int offset; } Buffer(int mark, int pos, int lim, int cap) { ... this.capacity = cap; limit(lim); // 設置limit position(pos); // 設置position if (mark >= 0) { ... this.mark = mark; } }
public final ByteBuffer put(byte[] src):將一個字節數組放入緩沖區。
每當放置一個字節時,position將會+1,保證position的值就是下一個可插入數據的buffer單元位置。源碼分析如下:
public final ByteBuffer put(byte[] src) { return put(src, 0, src.length); } // 由allocate方法調用分配緩沖區可知,返回的是Buffer的實現類HeapByteBuffer對象 public ByteBuffer put(byte[] src, int offset, int length) { checkBounds(offset, length, src.length); // 檢查是否下標越界 if (length > remaining()) // 檢查是否超出了可操作的數據范圍= limit-position throw new BufferOverflowException(); System.arraycopy(src, offset, hb, ix(position()), length); position(position() + length); // 重設position return this; }
public ByteBuffer get(byte[] dst):從緩沖區中讀取數據到 dst中。應在 flip() 方法后調用。
獲取數據,是在緩沖區字節數組中的position位置處開始,讀取一次完畢后,并會記錄當前讀取的位置,即position,以便于下一次調用get方法繼續讀取。
public ByteBuffer get(byte[] dst) { return get(dst, 0, dst.length); } // 調用HeapByteBuffer對象的get方法 public ByteBuffer get(byte[] dst, int offset, int length) { ... // 從緩沖區的字節數組final byte[] hb中,拷貝從 hb的 offset+position(注:offset=0) 處的長度為length的數據到 dst中 System.arraycopy(hb, ix(position()), dst, offset, length); position(position() + length); // 設置position return this; }
通過源碼分析可知,當put操作后,position記錄的是下一個可用的buffer單元,而get會從position位置處開始獲取數據,這顯然是無法獲得的,因此需要重新設置 position, 即 flip()方法。
public final Buffer flip() :翻轉緩沖區,在一個通道讀取或PUT操作序列之后,調用此方法以準備一個通道寫入或相對獲取操作的序列
將此通道的緩沖區的界限設置為當前position,保證了有可操作的數據。
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
public final Buffer mark():標記當前position
可用于在put操作轉get操作時標記當前的position位置,以便于調用reset方法從該位置繼續操作
public final Buffer mark() { mark = position; return this; }
public final Buffer reset():回到mark標記的位置
public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
public final Buffer clear():清除緩沖,重置初始化原始狀態
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
public final Buffer rewind():倒回,用于重新讀取數據
public final Buffer rewind() { position = 0; mark = -1; return this; }
直接緩沖區與間接緩沖區
間接緩沖:通過allocate方法分配的緩沖區。當程序發起read請求獲取磁盤文件時,該文件首先被OS讀取到內核地址空間中,并copy一份原始數據傳入JVM用戶地址空間,再傳給應用程序。增加了一個copy操作,導致效率降低。
直接緩沖:通過allocateDirecr方法分配的緩沖區,此緩沖區建立在物理內存中。直接在兩個空間中開辟內存空間,創建映射文件,去除了在內核地址空間和用戶地址空間中的copy操作,使得直接通過物理內存傳輸數據。雖然有效提高了效率,但是分配和銷毀該緩沖區的成本高于間接緩沖,且對于緩沖區中的數據將交付給OS管理,程序員無法控制。
通道Channel用于源節點與目標節點之間的連接,負責對緩沖區中的數據提供傳輸服務。
常用類
? FileChannel:用于讀取、寫入、映射和操作文件的通道。
? SocketChannel:通過 TCP 讀寫網絡中的數據。
? ServerSocketChannerl:通過 UDP 讀寫網絡中的數據通道。
? DatagramChannel:通過 UDP 讀寫網絡中的數據通道。
?
? 本地IO:FileInputStream、FileOutputStream、RandomAccessFile
? 網絡IO:Socket、ServerSocket、DatagramSocket
獲取Channel方式(以FileChannel為例)
? 1. Files.newByteChannel工具類靜態方法
? 2. getChannel方法:通過對象動態獲取,使用間接緩沖區。
FileInputStream fis = new FileInputStream(ORIGINAL_FILE); FileOutputStream fos = new FileOutputStream(OUTPUT_FILE); // 獲取通道 FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel(); // 提供緩沖區(間接緩沖區) ByteBuffer buffer = ByteBuffer.allocate(1024); while (inChannel.read(buffer) != -1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); }
? 3. 靜態open方法:使用open獲取到的Channel通道,使用直接緩沖區。
FileChannel inChannel = FileChannel.open(Paths.get(ORIGINAL_FILE), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get(OUTPUT_FILE), StandardOpenOption.READ, StandardOpenOption.CREATE, StandardOpenOption.WRITE); // 使用物理內存 內存映射文件 MappedByteBuffer inBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size()); MappedByteBuffer outBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size()); byte[] dst = new byte[inBuffer.limit()]; inBuffer.get(dst); outBuffer.put(dst);
// 使用DMA 直接存儲器存儲 inChannel.transferTo(0, inChannel.size(), outChannel); outChannel.transferFrom(inChannel, 0, inChannel.size());
public static FileChannel open(Path path, OpenOption... options):從path路徑中以某種方式獲取文件的Channel
StandardOpenOption | 描述 |
---|---|
CREATE | 創建一個新的文件,如果存在,則覆蓋。 |
CREATE_NEW | 創建一個新的文件,如果該文件已經存在則失敗。 |
DELETE_ON_CLOSE | 關閉時刪除。 |
DSYNC | 要求將文件內容的每次更新都與底層存儲設備同步寫入。 |
READ | 讀方式 |
SPARSE | 稀疏文件 |
SYNC | 要求將文件內容或元數據的每次更新都同步寫入底層存儲設備。 |
TRUNCATE_EXISTING | 如果文件已經存在,并且打開 wirte訪問,則其長度將截斷為0。 |
WRITE | 寫方式 |
APPEND | 如果文件以wirte訪問打開,則字節將被寫入文件的末尾而不是開頭。 |
public abstract MappedByteBuffer map(MapMode mode, long position, long size):將通道的文件區域映射到內從中。當操作較大的文件時,將數據映射到物理內存中才是值得的,因為映射到內存是需要開銷的。
FileChannel.MapMode | 描述 |
---|---|
PRIVATE | 專用映射模式(寫入時拷貝) |
READ_ONLY | 只讀模式 |
READ_WRIT | 讀寫模式 |
public abstract long transferFrom(ReadableByteChannel src, long position, long count):從給定的可讀取通道src,傳輸到本通道中。直接使用直接存儲器(DMA)對數據進行存儲。public abstract long transferTo(long position, long count, WritableByteChannel target):將本通道的文件傳輸到可寫入的target通道中。
分散(Scatter)與聚集(Gather)
? 分散讀取:將通道中的數據分散到多個緩沖區中。 public final long read(ByteBuffer[] dsts)
? 聚集寫入:將多個緩沖區中的數據聚集到一個Channel通道中。public final long write(ByteBuffer[] srcs)
字符集(Charset)
public final ByteBuffer encode(CharBuffer cb):編碼網絡通信的阻塞與非阻塞public final CharBuffer decode(ByteBuffer bb):解碼
阻塞是相對網絡傳輸而言的。傳統的IO流都是阻塞的,在網絡通信中,由于 IO 阻塞,需要為每一個客戶端創建一個獨立的線程來進行數據傳輸,性能大大降低;而NIO是非阻塞的,當存在空閑線程時,可以轉去操作其他通道,因此不必非要創建一個獨立的線程來服務每一個客戶端請求。
選擇器(Selector)
SelectableChannle對象的多路復用器,可同時對多個SelectableChannle對象的 IO 狀態監聽,每當創建一個Channel時,就向Selector進行注冊,交由Selector進行管理,只有Channel準備就緒時,Selector可會將任務分配給一個或多個線程去執行。Selector可以同時管理多個Channel,是非阻塞 IO 的核心。
NIO 阻塞式
服務器Server不斷監聽客戶端Client的請求,當建立了一個Channel時,服務器進行read操作,接收客戶端發送的數據,只有當客戶端斷開連接close,或者執行shutdownOutput操作時,服務器才知曉沒有數據了,否則會一直進行read操作;當客戶端在read操作獲取服務器的反饋時,若服務器沒有關閉連接或者shutdownInput時也會一直阻塞。示例代碼如下:
static final String ORIGINAL_FILE = "F:/1.png"; static final String OUTPUT_FILE = "F:/2.jpg";
public void server() throws Exception { // 打開TCP通道,綁定端口監聽 ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.bind(new InetSocketAddress(9988)); ByteBuffer buf = ByteBuffer.allocate(1024); // 獲取連接 SocketChannel accept = null; while ((accept= serverChannel.accept()) != null) { FileChannel fileChannel = FileChannel.open( Paths.get(OUTPUT_FILE), StandardOpenOption.CREATE, StandardOpenOption.WRITE); // 讀取客戶端的請求數據 while (accept.read(buf) != -1) { buf.flip(); fileChannel.write(buf); buf.clear(); } // 發送執行結果 buf.put("成功接收".getBytes()); buf.flip(); accept.write(buf); buf.clear(); fileChannel.close(); // 關閉連接,否則客戶端會一直等待讀取導致阻塞,可使用shutdownInput,但任務已結束,該close accept.close(); } serverChannel.close(); }
public void client() throws Exception { // 打開一個socket通道 SocketChannel clientChannel = SocketChannel.open( new InetSocketAddress("127.0.0.1", 9988)); // 創建緩沖區和文件傳輸通道 FileChannel fileChannel = FileChannel.open(Paths.get(ORIGINAL_FILE), StandardOpenOption.READ); ByteBuffer buf = ByteBuffer.allocate(1024); while ( fileChannel.read(buf) != -1) { buf.flip(); clientChannel.write(buf); buf.clear(); } // 關閉輸出(不關閉通道),告知服務器已經發送完畢,去掉下面一行代碼服務區將一直讀取導致阻塞 clientChannel.shutdownOutput(); int len = 0; while ((len = clientChannel.read(buf)) != -1) { buf.flip(); System.out.println(new String(buf.array(), 0, len)); buf.clear(); } fileChannel.close(); clientChannel.close(); }
NIO 非阻塞式
通過在通道Channel中調用configureBlocking將blocking設置為false,讓Channel可以進行異步 I/O 操作。
public void client() throws Exception { // 打開一個socket通道 SocketChannel clientChannel = SocketChannel.open( new InetSocketAddress("127.0.0.1", 9988)); ByteBuffer buf = ByteBuffer.allocate(1024); // 告知服務器,已經發送完畢 // clientChannel.shutdownOutput(); // 設置非阻塞 clientChannel.configureBlocking(Boolean.FALSE); buf.put("哈哈".getBytes()); buf.flip(); clientChannel.write(buf); clientChannel.close(); }
public void server() throws Exception { // 打開TCP通道,綁定端口監聽 ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(Boolean.FALSE); serverChannel.bind(new InetSocketAddress(9988)); // 創建一個Selector用于管理Channel Selector selector = Selector.open(); // 將服務器的Channel注冊到selector中,并添加 OP_ACCEPT 事件,讓selector監聽通道的請求 serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 一直判斷是否有已經準備就緒的Channel while (selector.select() > 0) { // 存在一個已經準備就緒的Channel,獲取SelectionKey集合中獲取觸發該事件的所有key Iteratorkeys = selector.selectedKeys().iterator(); while (keys.hasNext()) { SelectionKey sk = keys.next(); SocketChannel accept = null; ByteBuffer buffer = null; // 針對不同的狀態進行操作 if (sk.isAcceptable()) { // 可被連接,設置非阻塞并注冊到selector中 accept = serverChannel.accept(); accept.configureBlocking(Boolean.FALSE); accept.register(selector, SelectionKey.OP_READ); } else if (sk.isReadable()) { // 可讀,獲取該選擇器上的 Channel進行讀操作 accept = (SocketChannel) sk.channel(); buffer = ByteBuffer.allocate(1024); int len = 0; while ((len = accept.read(buffer)) != -1) { buffer.flip(); System.out.println(new String(buffer.array(), 0, len)); buffer.clear(); } } } // 移除本次操作的SelectionKey keys.remove(); } serverChannel.close(); }
方法使用說明
ServerSocketChannel對象只能注冊accept 事件。
設置configureBlocking為false,才能使套接字通道中進行異步 I/O 操作。
調用selectedKeys方法,返回發生了SelectionKey對象的集合。
調用remove方法,用于從SelectionKey集合中移除已經被處理的key,若不處理,那么它將繼續以當前的激活事件狀態繼續存在。
Pipe管道
Channel都是雙向通道傳輸,而Pipe就是為了實現單向管道傳送的通道對,有一個source通道(Pipe.SourceChannel)和一個sink通道(Pipe.SinkChannel)。sink用于寫數據,source用于讀數據。直接使用Pipe.open()獲取Pipe對象,操作和FileChannel一樣。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/73720.html
摘要:原文鏈接版本之前世今生最全篇語言語言是博士在創建年,被命名為提出了愿景公開版本個包文件,的類文件第一個版本發布在定義為代表技術虛擬機版本發布時間代表技術文件格式內部類反射版本發布時間從開始以后的版本定義為擴展到個包個類版本名稱為區分企業平 原文鏈接:Java版本之前世今生-最全篇 1.Oak 語言 Oak 語言是James Gosling 博士在1991創建 2.JDK Beta 1...
摘要:發布史年月日,公司正式發布語言,這一天是的生日。年月日,發布,成為語言發展史上的又一里程碑。年月,發布,三個版本分別改為,,,。年月日,以億美元收購公司,并取得了的版權。年月日,發布,并改用的命名方式。 特此聲明:本文為本人公司郭總原創書籍的前言,該書還未出版,作為該書籍的初版在接下來的時間里,將免費在本人微信公眾號內不間斷更新與大家一起學習閱讀。喜歡學習的小伙伴可以搜索微信公眾號:程...
摘要:中很多特性或者說知識點都是和面向對象編程概念相關的。在多線程中內容有很多,只是簡單說明一下中初步使用多線程需要掌握的知識點,以后有機會單獨再詳細介紹一些高級特性的使用場景。 寫這篇文章的目的是想總結一下自己這么多年來使用java的一些心得體會,主要是和一些java基礎知識點相關的,所以也希望能分享給剛剛入門的Java程序員和打算入Java開發這個行當的準新手們,希望可以給大家一些經...
摘要:編譯完成后,如果沒有報錯,那么通過命令對字節碼文件進行解釋運行,執行時不需要添加后綴總結說白了,整個程序對編寫運行有三步編寫為后綴對程序文件通過程序文件進行編譯生成文件文件名解釋運行寫代碼編譯解釋運行 前言 最近開始學習下java,畢竟web開發還是java比較完善功能也較php更加強大。學習資料參考:https://github.com/DuGuQiuBai... 此章主要記錄下...
摘要:時間年月日星期五說明本文部分內容均來自慕課網。線性堆疊式二維碼示意圖矩陣式二維碼在一個矩形空間通過黑白像素在矩陣中的不同分布進行編碼。 時間:2017年06月23日星期五說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com教學示例源碼:無個人學習源碼:https://github.com/zccodere/s... 第一章:二維碼的概念 1-1 二維碼概述...
閱讀 5766·2021-11-24 10:25
閱讀 2702·2021-11-16 11:44
閱讀 3860·2021-10-11 11:09
閱讀 3178·2021-09-02 15:41
閱讀 3261·2019-08-30 14:14
閱讀 2290·2019-08-29 14:10
閱讀 2354·2019-08-29 11:03
閱讀 1131·2019-08-26 13:47