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