NIO深入理解FileChannel使用方法原理
前言
前文我們已經(jīng)了解了NIO的三大核心組件,本篇文章會(huì)詳細(xì)介紹FileChannel中的使用方法和原理。
FileChannel
FileChannel是對(duì)一個(gè)文件讀,寫(xiě),映射,操作的Channel。FileChannel是線程安全的,可以被多個(gè)線程并發(fā)使用。同一個(gè)進(jìn)程中的多個(gè)FileChannel看到的同一個(gè)文件的視圖是相同的,由于底層操作系統(tǒng)執(zhí)行的緩存和網(wǎng)絡(luò)文件系統(tǒng)協(xié)議引起的延遲,不同進(jìn)程中在同一時(shí)間看到的文件視圖可能會(huì)不同。
FileChannel的類(lèi)圖如下,F(xiàn)ileChannel是一個(gè)抽象類(lèi),它的具體實(shí)現(xiàn)是FileChannelImpl。

FileChannel的創(chuàng)建
獲取FileChannel的方式有下面四種
- FileChannel.open()
直接調(diào)用FileChannel的open()方法,傳入Path即可獲得FileChannel
// 直接傳入Path默認(rèn)是只讀FileChannel
FileChannel fileChannel = FileChannel.open(Path.of("./tmp/linshifu.txt"));
// 和直接傳入Path相比,支持傳入OpenOption數(shù)組
FileChannel channel = FileChannel.open(Path.of("./tmp/linshifu.txt"), StandardOpenOption.WRITE);
OpenOption是一個(gè)空接口,我們可以傳入StandardOpenOption枚舉,StandardOpenOption有如下值
public enum StandardOpenOption implements OpenOption {
// 可讀Channel
READ,
// 可寫(xiě)Channel
WRITE,
// 如果Channel是可寫(xiě)(WRITE)的,緩沖中的數(shù)據(jù)會(huì)從文件末尾開(kāi)始寫(xiě),而不是從頭開(kāi)始寫(xiě)
APPEND,
// 如果Channel是可寫(xiě)(WRITE)的,文件的長(zhǎng)度會(huì)被置為0
TRUNCATE_EXISTING,
// 如果文件不存在,則會(huì)創(chuàng)建一個(gè)新的文件,如果配置了CREATE,則CREATE_NEW會(huì)失效
CREATE,
// 創(chuàng)建換一個(gè)新的文件,如果文件已經(jīng)存在,則會(huì)失敗
CREATE_NEW,
// Channel關(guān)閉時(shí)刪除
DELETE_ON_CLOSE,
// 稀疏文件
SPARSE,
// 要求對(duì)文件內(nèi)容或元數(shù)據(jù)的每次更新都同步寫(xiě)入基礎(chǔ)存儲(chǔ)設(shè)備。
SYNC,
// 要求對(duì)文件內(nèi)容的每次更新都同步寫(xiě)入基礎(chǔ)存儲(chǔ)設(shè)備。
DSYNC;
}
- FileInputStream.getChannel()
通過(guò)FileInputStream的getChannel()方法獲取FileChannel
FileInputStream fileInputStream = new FileInputStream("./tmp/linshifu.txt");
FileChannel fileChannel = fileInputStream.getChannel();
FileInputStream創(chuàng)建的FileChannel不可寫(xiě),只能讀
- FileOutputStream.getChannel()
通過(guò)FileOutputStream的getChannel()方法獲取FileChannel
FileOutputStream fileInputStream = new FileOutputStream("./tmp/linshifu.txt");
FileChannel fileChannel = fileInputStream.getChannel();
FileOutputStream創(chuàng)建FileChannel不可讀,只能寫(xiě)
- RandomAccessFile.getChannel()
通過(guò)RandomAccessFile的getChannel()方法獲取FileChannel
RandomAccessFile file = new RandomAccessFile("./tmp/linshifu.txt", "rw");
FileChannel fileChannel = file.getChannel();
RandomAccessFile中的模式
與OutputStream和InputStream不同的是創(chuàng)建RandomAccessFile需要傳入模式,RandomAccessFile的模式也會(huì)影響到FileChannel,創(chuàng)建RandomAccessFile可以傳入的模式有下面4種
r
只讀模式,創(chuàng)建的RandomAccessFile只能讀,如果使用只讀的RandomAccessFile創(chuàng)建的FileChannel寫(xiě)數(shù)據(jù)會(huì)拋出NonWritableChannelException
rw
讀寫(xiě)模式,創(chuàng)建的RandomAccessFile即可讀,也可寫(xiě)
rws
與rw一樣,打開(kāi)以進(jìn)行讀取和寫(xiě)入,并且還要求對(duì)文件內(nèi)容或元數(shù)據(jù)的每次更新同步寫(xiě)入基礎(chǔ)存儲(chǔ)設(shè)備
rwd
與rw一樣,打開(kāi)以進(jìn)行讀取和寫(xiě)入,并且還要求對(duì)文件內(nèi)容的每次更新都同步寫(xiě)入底層存儲(chǔ)設(shè)備
FileChannel操作文件
讀文件操作
讀文件的方法有如下三個(gè)
// 從position位置讀取ByteBuffer.capacity個(gè)byte,Channel的position向后移動(dòng)capacity個(gè)位置 public abstract int read(ByteBuffer dst) throws IOException; // 從傳入的position開(kāi)始讀取ByteBuffer.capacity個(gè)byte,Channel的positon位置不變 public abstract int read(ByteBuffer dst, long position) throws IOException; // 按順序讀取文件到ByteBuffer數(shù)組中,會(huì)將數(shù)據(jù)讀取到ByteBuffer數(shù)組的[offset,offset+length)的ByteBuf子數(shù)組中 public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
我們準(zhǔn)備一個(gè)
寫(xiě)一個(gè)demo
寫(xiě)文件操作
// 從position開(kāi)始寫(xiě)入ByteBuffer中的數(shù)據(jù)
public abstract int write(ByteBuffer src) throws IOException;
// 從position開(kāi)始將ByteBuffer數(shù)組的[offset,offset+length)的ByteBuf子數(shù)組中的數(shù)據(jù)寫(xiě)入文件
public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
// 從position開(kāi)始將ByteBuffer數(shù)組中的數(shù)據(jù)寫(xiě)入文件
public final long write(ByteBuffer[] srcs) throws IOException {
return write(srcs, 0, srcs.length);
}
寫(xiě)一個(gè)Demo
對(duì)文件的更新強(qiáng)制輸出到底層存儲(chǔ)設(shè)備
這種方式可以確保在系統(tǒng)崩缺時(shí)不會(huì)丟失數(shù)據(jù),參數(shù)中的boolean表示刷盤(pán)時(shí)是否將文件元數(shù)據(jù)也同時(shí)寫(xiě)到磁盤(pán)上。
public abstract void force(boolean metaData) throws IOException;
通道之間數(shù)據(jù)傳輸
如果需要將FileChannel的數(shù)據(jù)快速傳輸?shù)搅硪粋€(gè)Channel,可以使用transferTo和transFrom
// 將字節(jié)從此通道的文件傳輸?shù)浇o定的可寫(xiě)字節(jié)通道 public abstract long transferTo(long position, long count,WritableByteChannel target) throws IOException; // 將字節(jié)從給定的可讀字節(jié)通道傳輸?shù)酱送ǖ赖奈募? public abstract long transferFrom(ReadableByteChannel src,long position, long count) throws IOException;
通常情況下我們要將一個(gè)通道的數(shù)據(jù)傳到另一個(gè)通道。例如,從一個(gè)文件讀取數(shù)據(jù)通過(guò)socket通道進(jìn)行發(fā)送。比如通過(guò)http協(xié)議讀取服務(wù)器上的一個(gè)靜態(tài)文件,要經(jīng)過(guò)下面幾個(gè)階段
- 將文件從硬盤(pán)拷貝到頁(yè)緩沖區(qū)
- 從頁(yè)緩沖區(qū)讀拷貝到用戶(hù)緩沖區(qū)
- 用戶(hù)緩沖區(qū)的數(shù)據(jù)拷貝到socket內(nèi)核緩沖區(qū),最終再將socket內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到網(wǎng)卡中

當(dāng)我們通過(guò)transferTo或者transferFrom在通道之間傳輸數(shù)據(jù)時(shí),如果內(nèi)核支持,則會(huì)使用零拷貝的方式傳輸數(shù)據(jù)
零拷貝技術(shù)可以避免將數(shù)據(jù)拷貝到用戶(hù)空間中

MappedByteBuffer
MappedByteBuffer是NIO中應(yīng)對(duì)的操作大文件的方式,它的讀寫(xiě)性能極高,它是一種基于mmap的零拷貝方案,通常情況下可以映射出整個(gè)文件,如果文件比較大,也支持分段映射。這其初聽(tīng)起來(lái)似乎不過(guò)就是將整個(gè)文件讀到內(nèi)存中,但是事實(shí)上并不是這樣。 一般來(lái)說(shuō),只有文件中實(shí)際讀取或者寫(xiě)入的部分才會(huì)映射到內(nèi)存中。

mmap通過(guò)內(nèi)存映射,將文件映射到內(nèi)核緩沖區(qū),同時(shí),用戶(hù)空間可以共享內(nèi)核空間的數(shù)據(jù)。這樣在進(jìn)行網(wǎng)絡(luò)傳輸時(shí),就可以減少內(nèi)核空間到用戶(hù)空間的拷貝次數(shù)。mmap需要4次上下文切換,3次數(shù)據(jù)拷貝。
MappedByteBuffer使用的是虛擬內(nèi)存,因此分配(map)的內(nèi)存大小不受JVM的-Xmx參數(shù)限制。
MappedByteBuffer使用的方式也比較簡(jiǎn)單,首先我們準(zhǔn)備一個(gè)文件,文件內(nèi)容如下所示,文件大小34B

我們利用MappedByteBuffer寫(xiě)一段代碼,將上面文件的第一個(gè)字符改成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í)行完上面代碼之后,這個(gè)文件的第一個(gè)字符確實(shí)被改成了a,但是在文字的末尾也多了很多奇怪的符號(hào)

再次查看文件,發(fā)現(xiàn)文件大小變?yōu)榱?KB,我們?cè)谶M(jìn)行文件映射時(shí)應(yīng)當(dāng)注意文件的position和size不應(yīng)當(dāng)超出文件的范圍,否則可能導(dǎo)致"文件空洞",磁盤(pán)上物理文件中寫(xiě)入數(shù)據(jù)間產(chǎn)生間隙。

以上就是NIO深入理解FileChannel的詳細(xì)內(nèi)容,更多關(guān)于NIO深入理解FileChannel的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot整合Spring Cloud Kubernetes讀取ConfigMap支持自動(dòng)刷新配置的教程
這篇文章主要介紹了Springboot整合Spring Cloud Kubernetes讀取ConfigMap支持自動(dòng)刷新配置,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Java開(kāi)發(fā)崗位面試被問(wèn)到嵌套類(lèi)怎么辦
本篇文章主要介紹了深入理解Java嵌套類(lèi)和內(nèi)部類(lèi),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2021-07-07
關(guān)于@DS注解切換數(shù)據(jù)源失敗的原因?qū)崙?zhàn)記錄
項(xiàng)目配置了多個(gè)數(shù)據(jù)源,需要使用@DS注解來(lái)切換數(shù)據(jù)源,但是卻遇到了問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于@DS注解切換數(shù)據(jù)源失敗原因的相關(guān)資料,需要的朋友可以參考下2023-05-05
SpringBoot+Mybatis使用Mapper接口注冊(cè)的幾種方式
本篇博文中主要介紹是Mapper接口與對(duì)應(yīng)的xml文件如何關(guān)聯(lián)的幾種姿勢(shì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
Java super關(guān)鍵字用法實(shí)戰(zhàn)案例分析
這篇文章主要介紹了Java super關(guān)鍵字用法,結(jié)合具體案例形式分析了java super關(guān)鍵字調(diào)用父類(lèi)構(gòu)造方法、屬性及方法等相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-09-09
Springboot?多級(jí)緩存設(shè)計(jì)與實(shí)現(xiàn)方案
多級(jí)緩存是提升高并發(fā)系統(tǒng)性能的關(guān)鍵策略之一,它不僅能夠減少系統(tǒng)的響應(yīng)時(shí)間,提高用戶(hù)體驗(yàn),還能有效降低后端系統(tǒng)的負(fù)載,防止系統(tǒng)過(guò)載,這篇文章主要介紹了Springboot?多級(jí)緩存設(shè)計(jì)與實(shí)現(xiàn),需要的朋友可以參考下2024-02-02
Java實(shí)現(xiàn)任意進(jìn)制轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)任意進(jìn)制轉(zhuǎn)換的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

