NIO深入理解FileChannel使用方法原理
前言
前文我們已經了解了NIO的三大核心組件,本篇文章會詳細介紹FileChannel中的使用方法和原理。
FileChannel
FileChannel是對一個文件讀,寫,映射,操作的Channel。FileChannel是線程安全的,可以被多個線程并發(fā)使用。同一個進程中的多個FileChannel看到的同一個文件的視圖是相同的,由于底層操作系統執(zhí)行的緩存和網絡文件系統協議引起的延遲,不同進程中在同一時間看到的文件視圖可能會不同。
FileChannel的類圖如下,FileChannel是一個抽象類,它的具體實現是FileChannelImpl。
FileChannel的創(chuàng)建
獲取FileChannel的方式有下面四種
- FileChannel.open()
直接調用FileChannel的open()方法,傳入Path即可獲得FileChannel
// 直接傳入Path默認是只讀FileChannel FileChannel fileChannel = FileChannel.open(Path.of("./tmp/linshifu.txt")); // 和直接傳入Path相比,支持傳入OpenOption數組 FileChannel channel = FileChannel.open(Path.of("./tmp/linshifu.txt"), StandardOpenOption.WRITE);
OpenOption是一個空接口,我們可以傳入StandardOpenOption枚舉,StandardOpenOption有如下值
public enum StandardOpenOption implements OpenOption { // 可讀Channel READ, // 可寫Channel WRITE, // 如果Channel是可寫(WRITE)的,緩沖中的數據會從文件末尾開始寫,而不是從頭開始寫 APPEND, // 如果Channel是可寫(WRITE)的,文件的長度會被置為0 TRUNCATE_EXISTING, // 如果文件不存在,則會創(chuàng)建一個新的文件,如果配置了CREATE,則CREATE_NEW會失效 CREATE, // 創(chuàng)建換一個新的文件,如果文件已經存在,則會失敗 CREATE_NEW, // Channel關閉時刪除 DELETE_ON_CLOSE, // 稀疏文件 SPARSE, // 要求對文件內容或元數據的每次更新都同步寫入基礎存儲設備。 SYNC, // 要求對文件內容的每次更新都同步寫入基礎存儲設備。 DSYNC; }
- FileInputStream.getChannel()
通過FileInputStream的getChannel()方法獲取FileChannel
FileInputStream fileInputStream = new FileInputStream("./tmp/linshifu.txt"); FileChannel fileChannel = fileInputStream.getChannel();
FileInputStream創(chuàng)建的FileChannel不可寫,只能讀
- FileOutputStream.getChannel()
通過FileOutputStream的getChannel()方法獲取FileChannel
FileOutputStream fileInputStream = new FileOutputStream("./tmp/linshifu.txt"); FileChannel fileChannel = fileInputStream.getChannel();
FileOutputStream創(chuàng)建FileChannel不可讀,只能寫
- RandomAccessFile.getChannel()
通過RandomAccessFile的getChannel()方法獲取FileChannel
RandomAccessFile file = new RandomAccessFile("./tmp/linshifu.txt", "rw"); FileChannel fileChannel = file.getChannel();
RandomAccessFile中的模式
與OutputStream和InputStream不同的是創(chuàng)建RandomAccessFile需要傳入模式,RandomAccessFile的模式也會影響到FileChannel,創(chuàng)建RandomAccessFile可以傳入的模式有下面4種
r
只讀模式,創(chuàng)建的RandomAccessFile只能讀,如果使用只讀的RandomAccessFile創(chuàng)建的FileChannel寫數據會拋出NonWritableChannelException
rw
讀寫模式,創(chuàng)建的RandomAccessFile即可讀,也可寫
rws
與rw
一樣,打開以進行讀取和寫入,并且還要求對文件內容或元數據的每次更新同步寫入基礎存儲設備
rwd
與rw
一樣,打開以進行讀取和寫入,并且還要求對文件內容的每次更新都同步寫入底層存儲設備
FileChannel操作文件
讀文件操作
讀文件的方法有如下三個
// 從position位置讀取ByteBuffer.capacity個byte,Channel的position向后移動capacity個位置 public abstract int read(ByteBuffer dst) throws IOException; // 從傳入的position開始讀取ByteBuffer.capacity個byte,Channel的positon位置不變 public abstract int read(ByteBuffer dst, long position) throws IOException; // 按順序讀取文件到ByteBuffer數組中,會將數據讀取到ByteBuffer數組的[offset,offset+length)的ByteBuf子數組中 public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
我們準備一個
寫一個demo
寫文件操作
// 從position開始寫入ByteBuffer中的數據 public abstract int write(ByteBuffer src) throws IOException; // 從position開始將ByteBuffer數組的[offset,offset+length)的ByteBuf子數組中的數據寫入文件 public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException; // 從position開始將ByteBuffer數組中的數據寫入文件 public final long write(ByteBuffer[] srcs) throws IOException { return write(srcs, 0, srcs.length); }
寫一個Demo
對文件的更新強制輸出到底層存儲設備
這種方式可以確保在系統崩缺時不會丟失數據,參數中的boolean表示刷盤時是否將文件元數據也同時寫到磁盤上。
public abstract void force(boolean metaData) throws IOException;
通道之間數據傳輸
如果需要將FileChannel的數據快速傳輸到另一個Channel,可以使用transferTo
和transFrom
// 將字節(jié)從此通道的文件傳輸到給定的可寫字節(jié)通道 public abstract long transferTo(long position, long count,WritableByteChannel target) throws IOException; // 將字節(jié)從給定的可讀字節(jié)通道傳輸到此通道的文件中 public abstract long transferFrom(ReadableByteChannel src,long position, long count) throws IOException;
通常情況下我們要將一個通道的數據傳到另一個通道。例如,從一個文件讀取數據通過socket通道進行發(fā)送。比如通過http協議讀取服務器上的一個靜態(tài)文件,要經過下面幾個階段
- 將文件從硬盤拷貝到頁緩沖區(qū)
- 從頁緩沖區(qū)讀拷貝到用戶緩沖區(qū)
- 用戶緩沖區(qū)的數據拷貝到socket內核緩沖區(qū),最終再將socket內核緩沖區(qū)的數據拷貝到網卡中
當我們通過transferTo
或者transferFrom
在通道之間傳輸數據時,如果內核支持,則會使用零拷貝的方式傳輸數據
零拷貝技術可以避免將數據拷貝到用戶空間中
MappedByteBuffer
MappedByteBuffer是NIO中應對的操作大文件的方式,它的讀寫性能極高,它是一種基于mmap的零拷貝方案,通常情況下可以映射出整個文件,如果文件比較大,也支持分段映射。這其初聽起來似乎不過就是將整個文件讀到內存中,但是事實上并不是這樣。 一般來說,只有文件中實際讀取或者寫入的部分才會映射到內存中。
mmap通過內存映射,將文件映射到內核緩沖區(qū),同時,用戶空間可以共享內核空間的數據。這樣在進行網絡傳輸時,就可以減少內核空間到用戶空間的拷貝次數。mmap需要4次上下文切換,3次數據拷貝。
MappedByteBuffer使用的是虛擬內存,因此分配(map)的內存大小不受JVM的-Xmx
參數限制。
MappedByteBuffer使用的方式也比較簡單,首先我們準備一個文件,文件內容如下所示,文件大小34B
我們利用MappedByteBuffer寫一段代碼,將上面文件的第一個字符改成a
,代碼如下
public static void main(String[] args) throws Exception { RandomAccessFile file = new RandomAccessFile("./tmp/01.txt", "rw"); MappedByteBuffer mbf = file.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1024); mbf.put(0, (byte) 'a'); file.close(); }
執(zhí)行完上面代碼之后,這個文件的第一個字符確實被改成了a
,但是在文字的末尾也多了很多奇怪的符號
再次查看文件,發(fā)現文件大小變?yōu)榱?KB,我們在進行文件映射時應當注意文件的position和size不應當超出文件的范圍,否則可能導致"文件空洞",磁盤上物理文件中寫入數據間產生間隙。
以上就是NIO深入理解FileChannel的詳細內容,更多關于NIO深入理解FileChannel的資料請關注腳本之家其它相關文章!
相關文章
解決java?try?throw?exception?finally遇上return?break?conti
這篇文章主要介紹了解決java?try?throw?exception?finally遇上return?break?continue造成異常丟失問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11zookeeper+Springboot實現服務器動態(tài)上下線監(jiān)聽教程詳解
這篇文章主要介紹了zookeeper+Springboot實現服務器動態(tài)上下線監(jiān)聽,主要介紹了什么是服務器動態(tài)上下線監(jiān)聽及為什么要實現對服務器上下線的監(jiān)聽,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-06-06SpringBoot利用@Retryable注解實現接口重試
本文主要介紹了springboot如何利用@Retryable注解實現接口重試功能,文中示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-06-06