Java文件IO操作教程之DirectIO的意義
前言
在前文《文件IO操作的一些最佳實(shí)踐》中,我介紹了一些 Java 中常見(jiàn)的文件操作的接口,并且就 PageCache 和 DIrect IO 進(jìn)行了探討,最近我自己封裝了一個(gè) Direct IO 的庫(kù),趁著這個(gè)機(jī)會(huì),本文重點(diǎn)談?wù)?Java 中 Direct IO 的意義,以及簡(jiǎn)單介紹下我自己的輪子。
Java 中的 Direct IO
如果你閱讀過(guò)我之前的文章,應(yīng)該已經(jīng)了解 Java 中常用的文件操作接口為:FileChannel,并且沒(méi)有直接操作 Direct IO 的接口。這也就意味著 Java 無(wú)法繞開(kāi) PageCache 直接對(duì)存儲(chǔ)設(shè)備進(jìn)行讀寫,但對(duì)于使用 Java 語(yǔ)言來(lái)編寫的數(shù)據(jù)庫(kù),消息隊(duì)列等產(chǎn)品而言,的確存在繞開(kāi) PageCache 的需求:
- PageCache 屬于操作系統(tǒng)層面的概念,用戶層面很難干預(yù),User BufferCache 顯然比 Kernel PageCache 要可控
- 現(xiàn)代操作系統(tǒng)會(huì)使用盡可能多的空閑內(nèi)存來(lái)充當(dāng) PageCache,當(dāng)操作系統(tǒng)回收 PageCache 內(nèi)存的速度低于應(yīng)用寫緩存的速度時(shí),會(huì)影響磁盤寫入的速率,直接表現(xiàn)為寫入 RT 增大,這被稱之為“毛刺現(xiàn)象”
PageCache 可能會(huì)好心辦壞事,采用 Direct IO + 自定義內(nèi)存管理機(jī)制會(huì)使得產(chǎn)品更加的可控,高性能。
Direct IO 的限制
在 Java 中使用 Direct IO 最終需要調(diào)用到 c 語(yǔ)言的 pwrite 接口,并設(shè)置 O_DIRECT flag,使用 O_DIRECT 存在不少限制
- 操作系統(tǒng)限制:Linux 操作系統(tǒng)在 2.4.10 及以后的版本中支持 O_DIRECT flag,老版本會(huì)忽略該 Flag;Mac OS 也有類似于 O_DIRECT 的機(jī)制
- 用于傳遞數(shù)據(jù)的緩沖區(qū),其內(nèi)存邊界必須對(duì)齊為 blockSize 的整數(shù)倍
- 用于傳遞數(shù)據(jù)的緩沖區(qū),其傳遞數(shù)據(jù)的大小必須是 blockSize 的整數(shù)倍。
- 數(shù)據(jù)傳輸?shù)拈_(kāi)始點(diǎn),即文件和設(shè)備的偏移量,必須是 blockSize 的整數(shù)倍
查看系統(tǒng) blockSize 大小的方式:stat /boot/|grep “IO Block”
ubuntu@VM-30-130-ubuntu:~$ stat /boot/|grep “IO Block”
Size: 4096 Blocks: 8 IO Block: 4096 directory通常為 4kb
Java 使用 Direct IO
項(xiàng)目地址
https://github.com/lexburner/kdio
引入依賴
<dependency> <groupId>moe.cnkirito.kdio</groupId> <artifactId>kdio-core</artifactId> <version>1.0.0</version> </dependency>
注意事項(xiàng)
// file path should be specific since the different file path determine whether your system support direct io public static DirectIOLib directIOLib = DirectIOLib.getLibForPath("/"); // you should always write into your disk the Integer-Multiple of block size through direct io. // in most system, the block size is 4kb private static final int BLOCK_SIZE = 4 * 1024;
Direct IO 寫
private static void write() throws IOException { if (DirectIOLib.binit) { ByteBuffer byteBuffer = DirectIOUtils.allocateForDirectIO(directIOLib, 4 * BLOCK_SIZE); for (int i = 0; i < BLOCK_SIZE; i++) { byteBuffer.putInt(i); } byteBuffer.flip(); DirectRandomAccessFile directRandomAccessFile = new DirectRandomAccessFile(new File("./database.data"), "rw"); directRandomAccessFile.write(byteBuffer, 0); } else { throw new RuntimeException("your system do not support direct io"); } }
Direct IO 讀
public static void read() throws IOException { if (DirectIOLib.binit) { ByteBuffer byteBuffer = DirectIOUtils.allocateForDirectIO(directIOLib, 4 * BLOCK_SIZE); DirectRandomAccessFile directRandomAccessFile = new DirectRandomAccessFile(new File("./database.data"), "rw"); directRandomAccessFile.read(byteBuffer, 0); byteBuffer.flip(); for (int i = 0; i < BLOCK_SIZE; i++) { System.out.print(byteBuffer.getInt() + " "); } } else { throw new RuntimeException("your system do not support direct io"); } }
主要 API
- DirectIOLib.java 提供 Native 的 pwrite 和 pread
- DirectIOUtils.java 提供工具類方法,比如分配 Block 對(duì)齊的 ByteBuffer
- DirectChannel/DirectChannelImpl.java 提供對(duì) fd 的 Direct 包裝,提供類似 FileChannel 的讀寫 API。
- DirectRandomAccessFile.java 通過(guò) DIO 的方式打開(kāi)文件,并暴露 IO 接口。
總結(jié)
這個(gè)簡(jiǎn)單的 Direct IO 框架參考了smacke/jaydio,這個(gè)庫(kù)自己搞了一套 Buffer 接口跟 JDK 的類庫(kù)不兼容,且讀寫實(shí)現(xiàn)里面加了一塊 Buffer 用于緩存內(nèi)容至 Block 對(duì)齊有點(diǎn) Direct IO 的語(yǔ)義。同時(shí),感謝塵央同學(xué)的指導(dǎo),這個(gè)小輪子的代碼量并不多,初始代碼引用自他的一個(gè)小 demo(已獲得本人授權(quán))。為什么需要這么一個(gè)庫(kù)?主要是考慮后續(xù)會(huì)出現(xiàn)像「中間件性能挑戰(zhàn)賽」和「PolarDB性能挑戰(zhàn)賽」這樣的比賽,Java 本身的 API 可能不足以發(fā)揮其優(yōu)勢(shì),如果有一個(gè)庫(kù)可以屏蔽掉 Java 和 CPP 選手的差距,豈不是美哉?我也將這個(gè)庫(kù)發(fā)到了中央倉(cāng)庫(kù),方便大家在自己的代碼中引用。
后續(xù)會(huì)視需求,會(huì)這個(gè)小小的輪子增加注入 fadvise,mmap 等系統(tǒng)調(diào)用的映射,也歡迎對(duì)文件操作感興趣的同學(xué)一起參與進(jìn)來(lái),pull request & issue are welcome!
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
java構(gòu)造器 默認(rèn)構(gòu)造方法及參數(shù)化構(gòu)造方法
構(gòu)造器也叫構(gòu)造方法、構(gòu)造函數(shù),是一種特殊類型的方法,負(fù)責(zé)類中成員變量(域)的初始化。構(gòu)造器的用處是在創(chuàng)建對(duì)象時(shí)執(zhí)行初始化,當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),系統(tǒng)會(huì)為這個(gè)對(duì)象的實(shí)例進(jìn)行默認(rèn)的初始化,下面文章將進(jìn)入講解,需要的朋友可以參考下2021-10-10Java將byte[]轉(zhuǎn)圖片存儲(chǔ)到本地的案例
這篇文章主要介紹了Java將byte[]轉(zhuǎn)圖片存儲(chǔ)到本地的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10java 實(shí)現(xiàn)下壓棧的操作(能動(dòng)態(tài)調(diào)整數(shù)組大小)
這篇文章主要介紹了java 實(shí)現(xiàn)下壓棧的操作(能動(dòng)態(tài)調(diào)整數(shù)組大小),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02Java中反射動(dòng)態(tài)代理接口的詳解及實(shí)例
這篇文章主要介紹了Java中反射動(dòng)態(tài)代理接口的詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04springBoot+mybatis-plus實(shí)現(xiàn)監(jiān)聽(tīng)mysql數(shù)據(jù)庫(kù)的數(shù)據(jù)增刪改
mybatis-plus技術(shù)是簡(jiǎn)化了繁瑣的代碼操作,把增刪改查的語(yǔ)句都內(nèi)置了,直接調(diào)用就可以實(shí)現(xiàn)數(shù)據(jù)庫(kù)的增刪改查了,這篇文章主要給大家介紹了關(guān)于springBoot+mybatis-plus實(shí)現(xiàn)監(jiān)聽(tīng)mysql數(shù)據(jù)庫(kù)數(shù)據(jù)增刪改的相關(guān)資料,需要的朋友可以參考下2024-01-01Java 不使用第三方變量交換兩個(gè)變量值的四種方法詳解
這篇文章主要介紹了四種不使用第三方變量交換兩個(gè)變量值的方法。文中對(duì)于四種方法進(jìn)行了詳細(xì)的分析,需要的小伙伴們可以跟隨小編一起學(xué)習(xí)一下2021-12-12Java中FTPClient上傳中文目錄、中文文件名亂碼問(wèn)題解決方法
這篇文章主要介紹了Java中FTPClient上傳中文目錄、中文文件名亂碼問(wèn)題解決方法,本文使用apache-commons-net工具包時(shí)遇到這個(gè)問(wèn)題,解決方法很簡(jiǎn)單,需要的朋友可以參考下2015-05-05