NIO(三)基礎
NIO是什麼?
它是Java1.4之後出現的IO API,與傳統IO和網路API不同,具有非阻塞的特點。
在BIO中我們使用位元組流和字元流。NIO中我們使用channel和buffer。資料總是從一個channel中讀取到buffer中,或者從buffer中寫入到channel中。
NIO的意思是一個執行緒可以讓一個channel將資料讀取到buffer中,與此同時,這個執行緒還可以做其他的事情,執行緒可以等到資料全部進入buffer之後再處理資料,從buffer中寫入執行緒也是一樣的。
selector:選擇器是一個NIO當中的概念,指的是一個物件,能監視多個channel發生的事件(如連線建立,資料到達等)。因此,一個單執行緒可以監視多個channel的資料。
Java NIO 總覽
Java NIO的三個核心基礎元件,
- Channels
- Buffers
- Selectors
其餘的諸如Pipe,FileLcok都是在使用以上三個核心元件時幫助更好使用的工具類。
Channels和Buffers的關係
所有的IO操作在NIO中都是以Channel開始的。一個Channel就像一個流。從Channel中,資料可以被讀取到buffer裡,也可以從buffer裡寫到Channel中。
基本的Channel實現有以下這些:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
涵蓋了UDP,TCP以及檔案的IO操作。
核心的buffer實現有這些
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
涵蓋了所有的基本資料型別(4類8種,除了Boolean)。也有其他的buffer如MappedByteBuffer,此處不講。
selectors
selector允許一個執行緒來監視多個Channel,這在當你的應用建立了多個連線,但是每個連線吞吐量都較小的時候是可行的。例如:一個聊天伺服器。圖為一個執行緒使用selector處理三個channel。
要使用一個Selector,你要先註冊這個selector的Channels。然後你呼叫selector的select()方法。這個方法會阻塞,直到它註冊的channels當中有一個準備好了的事件發生了。當select()方法返回的時候,執行緒可以處理這些事件,如新的連線的到來,資料收到了等。
NIO Channels
NIO channel和流很近似但是也有一些不同。
- 你既可以讀取也可以寫入到channel,流只能讀取或者寫入,inputStream和outputStream。
- channel可以非同步地讀和寫。
- channel永遠都是從一個buffer中讀或者寫入到一個buffer中去。
channel的實現
以下是NIO中最重要的幾個channel的實現。
- FileChannel 向檔案當中讀寫資料。
- DatagramChannel 通過UDP協議向網路讀寫資料
- SocketChannel 通過TCP協議向網路讀寫資料
- ServerSocketChannel 以一個web伺服器的形式,監聽到來的TCP連線,對每個連線建立一個SocketChannel。
一個簡單的channel例子
使用一個FileChannel將資料讀入一個buffer
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
buf.flip()的意思是讀寫轉換,首先你讀入一個buffer,然後你flip,轉換讀寫,然後再從buffer中讀出,buffer的操作接下來會講。
NIO buffer
NIO buffer在與NIO Channel互動時使用,資料從channel中讀取出來放入buffer,或者從buffer中讀取出來寫入channel。
buffer就是一塊記憶體,你可以寫入資料,並且在之後讀取它。這塊記憶體被包裝成NIO buffer物件,它提供了一些方法來讓你更簡單地操作記憶體。
buffer的基本使用
使用buffer讀寫資料基本上分為以下4部操作:
- 將資料寫入buffer
- 呼叫buffer.flip()
- 將資料從buffer中讀取出來
- 呼叫buffer.clear()或者buffer.compact()
在寫buffer的時候,buffer會跟蹤寫入了多少資料,需要讀buffer的時候,需要呼叫flip()來將buffer從寫模式切換成讀模式,讀模式中只能讀取寫入的資料,而非整個buffer。
當資料都讀完了,你需要清空buffer以供下次使用,可以有2種方法來操作:
- 呼叫clear()
- 呼叫compact()
區別:clear方法清空整個buffer,compact方法只清除你已經讀取的資料,未讀取的資料會被移到buffer的開頭,此時寫入資料會從當前資料的末尾開始。
一個簡單的buffer使用例子:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//建立一個容量為48的ByteBuffer
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //從channel中讀(取資料然後寫)入buffer
//下面是讀取buffer
while (bytesRead != -1) {
buf.flip(); //轉換buffer為讀模式
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // 一次讀取一個byte
}
buf.clear(); //清空buffer準備下一次寫入
bytesRead = inChannel.read(buf);
}
aFile.close();
buffer的Capacity,Position和Limit
buffer有3個屬性需要熟悉以理解buffer的工作原理:
- 容量(Capacity):緩衝區能夠容納的資料元素的最大數量。容量在緩衝區建立時被設定,並且永遠不能被改變。
- 上界(Limit):寫模式中等價於buffer的大小,即capacity;讀模式中為當前緩衝區中一共有多少資料,即可讀的最大位置。這意味著當呼叫filp()方法切換成讀模式時,limit的值變成position的值,而position重新指向0.
- 位置(Position):下一個要被讀或寫的元素的位置。初始化為0,buffer滿時,position最大值為capacity-1。切換成讀模式的時候,position指向0。Position會自動由相應的 get( )和 put( )函式更新。
position和limit的值在讀/寫模式中是不一樣的。
capacity的值永遠表示buffer的大小。
下圖解釋了在讀/寫模式中Capacity,Position和Limit的意思。
buffer的種類
Java NIO中有以下這些buffer種類:
- ByteBuffer
- MappedByteBuffer //比較特殊,會在以後講解
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
建立一個buffer
獲得一個buffer 之前必須先分配一塊記憶體,每個buffer類都有一個靜態方法allocate() 來做這件事。
下例為建立一個容量為48byte的ByteBuffer:ByteBuffer buf = ByteBuffer.allocate(48);
建立一個1024個字元的CharBufferCharBuffer buf = CharBuffer.allocate(1024);
將資料寫入buffer
寫入buffer的方法有2種:
- 1.從一個channel中寫入buffer。
- 2.呼叫buffer的put()方法來自行寫入資料。
例:
int bytesRead = inChannel.read(buf); //從channel讀入buffer
buf.put(127); //自行寫入buffer
put方法有很多的過載形式。以供你用各種不同的方法寫入buffer中,比如從一個特定的position,或者寫入一個array,詳見JavaDoc。
flip()
flip方法將寫模式切換成讀模式,呼叫flip()方法會將limit設定為position,將position設定回0。
換句話說,position標誌著寫模式中寫到哪裡,切換成讀模式之後,limit標誌著之前寫到哪裡,也就是現在能讀到哪裡。
從buffer中讀取資料
有2種方法可以從buffer中讀取資料。
- 1.從buffer中讀取資料到channel中。
- 2.使用buffer的get()方法自行從buffer中讀出資料。
例子:
//從buffer中讀取資料到channel中
int bytesWritten = inChannel.write(buf);
//使用buffer的get()方法自行從buffer中讀出資料
byte aByte = buf.get();
get方法有很多的過載形式。以供你用各種不同的方法讀取buffer中的資料。例如從特定位置讀取資料,或者讀一個陣列出來。詳見JavaDoc。
rewind()
rewind()方法將position設定為0,但是不會動buffer裡的資料,這樣可以從頭開始重新讀取資料,limit的值不會變,這意味著limit依舊標誌著能讀多少資料。
clear()和compact()
當你讀完所有的資料想要重新寫入資料時,你可以呼叫clear或者compact方法。
當你呼叫clear()方法的時候,position被設定為0,limit被設定為capacity,換句話說,buffer的資料雖然都還在,但是buffer被初始化了,處於可以被重寫的狀態。
這也就意味著如果buffer中還有沒被讀取的資料,在執行clear之後,你無法知道資料讀到哪兒了,剩下的資料還有多少。
如果還有沒有讀完的資料,但是你想先寫資料,可以用compact()方法,這樣未讀資料會放在buffer前端,可以在未讀資料之後跟著寫新的資料。compact()會複製未讀資料到buffer前端,然後設定position為未讀資料單位後面緊跟的位置。limit還是設定為capacity,這和clear是一樣的。現在buffer處於可以寫的狀態,但是不會覆蓋之前未讀完的資料。
mark()和reset()
你可以通過呼叫buffer.mark()來mark一個buffer中給定的位置。然後你就可以用buffer.reset()方法來講position設定回之前mark的位置。
例子:
buffer.mark();
//呼叫buffer.get()方法若干次,e.g. 比如在做parsing的時候
buffer.reset(); //set position back to mark.
equals() 和 compareTo()
使用這2種方法能夠比較2個buffer。
equals()
equals()方法用於判斷2個buffer是否相等,2個buffer是equal的,當它們:
- 是同一種資料型別的buffer。
- buffer中未讀取的bytes,chars等資料個數是一樣的,即(limit-position)相等,capacity不需要相等,剩餘資料的索引也不需要相等。
- 未讀取的bytes,chars等內容是一模一樣的,即各自[position,limit-1]索引的資料要完全相等。
如你所見,equals()方法只比較buffer的部分內容,而不是buffer中所有的資料,事實上,它只比較buffer中剩餘的元素是否一樣。
compareTo()
compareTo()方法比較兩個buffer的剩餘元素(位元組,字元等),用於例如: 排序。
在下列情況下,緩衝區被認為比另一個緩衝區“小”:
比較是針對每個緩衝區你剩餘資料(從 position 到 limit)進行的,與它們在 equals() 中的方式相同,直到不相等的元素被發現或者到達緩衝區的上界。如果一個緩衝區在不相等元素發現前已經被耗盡,較短的緩衝區被認為是小於較長的緩衝區。
if (buffer1.compareTo(buffer2) < 0) {
// do sth, it means buffer2 < buffer1,not buffer1 < buffer2
doSth();
}
相關文章
- NIO相關基礎篇三
- NIO基礎
- 【死磕NIO】— NIO基礎詳解
- java基礎-java NIOJava
- NIO相關基礎篇一
- NIO相關基礎篇二
- NIO學習三、基於NIO的WEB伺服器Web伺服器
- Java基礎(Socket通訊和NIO)Java
- 深入學習Netty(一)NIO基礎篇Netty
- 【基礎篇索引】索引基礎(三)索引
- Asyncdb(三):Java NIOJava
- 逆向基礎(三)
- Dart基礎(三)Dart
- c 基礎三
- Golang 基礎之基礎語法梳理 (三)Golang
- Kotlin基礎三Kotlin
- 儲存基礎(三)
- 三維重建基礎
- NIO,一本難唸的經——分散式系統基礎分散式
- ADT基礎(三)—— HashMapHashMap
- DataBinding基礎使用三
- Python (三) 基礎資訊Python
- Three.js基礎(三)JS
- 前端基礎(三):函式前端函式
- Struts1 基礎(三)
- 【調優】CBO基礎(三)
- typeScript 基礎型別 (三)TypeScript型別
- 《java程式設計基礎》java的基礎知識(三)Java程式設計
- 基礎架構遷雲(三)架構
- Flutter 學習(三) dart基礎FlutterDart
- JavaScript夯實基礎系列(三):thisJavaScript
- Nginx深入瞭解-基礎(三)Nginx
- python 基礎語法(三)Python
- Python基礎(三)數字Python
- python基礎(三)——操作列表Python
- c++基礎三(變數)C++變數
- Java面試題基礎篇(三)Java面試題
- Python scrapy基礎教程(三)Python