一文探索Java文件讀寫更高效方式
背景
最近在探秘kafka為什么如此快?其背后的秘訣又是什么?
懷著好奇之心,開始像剝洋蔥 一樣逐層內(nèi)嵌。一步步揭曉kafka能夠吊打mq的真因。了解之后不得不說kafka:yyds。
了解到順序存盤的運(yùn)用
探測(cè)到稀疏索引的引進(jìn)
知曉其零拷貝技術(shù)的威力
嗅覺到mmp(內(nèi)存映射文件)的神來之筆
......
mmp如此神奇,那么運(yùn)用于文件壓縮,是否同樣可以實(shí)現(xiàn)飛速壓縮呢?
又懷著好奇之心,決定用實(shí)際行動(dòng)證明這個(gè)結(jié)論(否則我們的知識(shí)只能紙上談兵)
編碼是我們的本能功能,好奇之心是我們永遠(yuǎn)的利器。不能丟
曾幾何時(shí),有位BA告訴我他的經(jīng)歷:DEV轉(zhuǎn)為BA后,代碼就生疏了,后來他強(qiáng)迫自己每個(gè)迭代都領(lǐng)一個(gè)小需求鞭策自己。
曾幾何時(shí),有位前輩告訴我:即使你以后成長(zhǎng)為架構(gòu)師甚至更高職位,也不能丟失編碼這件神器。否則你會(huì)發(fā)現(xiàn)會(huì)很尷尬——會(huì)被人稱為“需求翻譯機(jī)”
......
這不是心靈雞湯,這是來自靈魂的諫言,我深刻了解到:編碼真的是學(xué)到老活到老的工作。
看到很多優(yōu)秀的同事離職遠(yuǎn)去,通過交流感觸更加深厚
所以,大家一定記得:學(xué)會(huì)一個(gè)知識(shí)要努力應(yīng)用一遍。這樣才能記得牢固;在學(xué)習(xí)中要不求甚解,完全知道這個(gè)知識(shí)也要知道為什么這么做
......
場(chǎng)景分析
場(chǎng)景1:小文件單文件壓縮
- 1、原始文件介紹:63.7M、 csv文件 、單個(gè)文件
- 2、對(duì)比技術(shù)介紹:網(wǎng)上流傳、使用緩沖區(qū)、使用管道、使用mmp
- 3、對(duì)比結(jié)果展示:
方式1:網(wǎng)上流傳(流傳在坊間的神話,其實(shí)是帶刺的玫瑰)
小王剛?cè)肼毑痪茫幸惶焱蝗唤拥叫枨?,要壓縮文件,之前沒寫過,怎么辦?這個(gè)時(shí)候會(huì)在網(wǎng)上搜到這個(gè)方法
執(zhí)行結(jié)果(效率很嚇人)
zipMethod=withoutBuffer
costTime=327000ms
代碼如下:
public void zipFileWithoutBuffer(String outFile){
long beginTime = System.currentTimeMillis();
File zipFile = new File(outFile);
File inputFile = new File(INPUT_FILE);
try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile))) {
try (InputStream inputStream = new FileInputStream(inputFile)){
zipOutputStream.putNextEntry(new ZipEntry(inputFile.getName()));
int temp;
while ((temp = inputStream.read()) != -1){
zipOutputStream.write(temp);
}
}
printResult(beginTime,"withoutBuffer");
} catch (Exception e) {
e.printStackTrace();
System.out.println("error" + e.getMessage());
}
}方式2:使用緩沖區(qū)
小王很開心,提交代碼,翻轉(zhuǎn)了需求狀態(tài),可驗(yàn)收。
小花是團(tuán)隊(duì)資深技術(shù)達(dá)人,走查代碼發(fā)現(xiàn)一臉懵逼:網(wǎng)上搜的?這個(gè)會(huì)很慢,你再研究研究
小王又換了一種思路,借助緩沖區(qū)BufferedOutputStream
執(zhí)行結(jié)果(快了很多)
zipMethod=withBuffer
costTime=5170ms
代碼如下:
public void zipFileWithBuffer(String outFile){
long beginTime = System.currentTimeMillis();
File zipFile = new File(outFile);
File inputFile = new File(INPUT_FILE);
try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(zipOutputStream)) {
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(inputFile))){
zipOutputStream.putNextEntry(new ZipEntry(inputFile.getName()));
int temp;
while ((temp = bufferedInputStream.read()) != -1){
bufferedOutputStream.write(temp);
}
}
printResult(beginTime,"withBuffer");
} catch (Exception e) {
e.printStackTrace();
System.out.println("error" + e.getMessage());
}
}方式3:使用通道
小王懷著忐忑的心情,又一次召集大家走查代碼。
小花:速度要求沒那么高,這樣做已經(jīng)差不多了,代碼可以提交了
其實(shí)最近研究kafka,接觸過nio,知曉:nio有種技術(shù)叫通道:Channel
執(zhí)行結(jié)果(好快)
zipMethod=withChannel
costTime=1642ms
代碼如下:
public void zipFileWithChannel(String outFile){
long beginTime = System.currentTimeMillis();
File zipFile = new File(outFile);
File inputFile = new File(INPUT_FILE);
try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
WritableByteChannel writableByteChannel = Channels.newChannel(zipOutputStream)) {
try (FileChannel fileChannel = new FileInputStream(inputFile).getChannel()){
zipOutputStream.putNextEntry(new ZipEntry(inputFile.getName()));
fileChannel.transferTo(0,inputFile.length(),writableByteChannel);
}
printResult(beginTime,"withChannel");
} catch (Exception e) {
e.printStackTrace();
System.out.println("error" + e.getMessage());
}
}方式4:使用mmp
研究kafka過程中,不止知曉nio有種技術(shù)叫通道:Channel,還有種技術(shù)叫mmp
執(zhí)行結(jié)果(好快)
zipMethod=withMmp
costTime=1554ms
代碼如下:
public void zipFileWithMmp(String outFile){
long beginTime = System.currentTimeMillis();
File zipFile = new File(outFile);
File inputFile = new File(INPUT_FILE);
try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
WritableByteChannel writableByteChannel = Channels.newChannel(zipOutputStream)) {
zipOutputStream.putNextEntry(new ZipEntry(inputFile.getName()));
MappedByteBuffer mappedByteBuffer = new RandomAccessFile(INPUT_FILE,"r").getChannel()
.map(FileChannel.MapMode.READ_ONLY,0,inputFile.length());
writableByteChannel.write(mappedByteBuffer);
printResult(beginTime,"withMmp");
} catch (Exception e) {
e.printStackTrace();
System.out.println("error" + e.getMessage());
}
}場(chǎng)景2:大文件單文件壓縮
- 1、原始文件介紹:585M、 csv文件 、單個(gè)文件
- 2、對(duì)比技術(shù)介紹:使用緩沖區(qū)、使用管道、使用mmp
- 3、對(duì)比結(jié)果展示:
| 使用緩沖區(qū) | 使用通道 | 使用mmp |
|---|---|---|
| costTime=46034ms | costTime=11885ms | costTime=10810ms |
場(chǎng)景3:大文件多文件壓縮
- 1、原始文件介紹:585M、 csv文件 、5個(gè)文件
- 2、對(duì)比技術(shù)介紹:使用緩沖區(qū)、使用管道、使用mmp
- 3、對(duì)比結(jié)果展示:
| 使用緩沖區(qū) | 使用通道 | 使用mmp |
|---|---|---|
| costTime=173122ms | costTime=53982ms | costTime=50543ms |
分析結(jié)論
1、對(duì)比見下表
| 壓縮場(chǎng)景 | 網(wǎng)上流傳 | 使用緩沖區(qū) | 使用通道 | 使用mmp |
|---|---|---|---|---|
| 場(chǎng)景1:小文件單文件壓縮(60M) | 327000ms | 5170ms | 1642ms | 1554ms |
| 場(chǎng)景2:大文件單文件壓縮(585M) | -- | 46034ms | 11885ms | 10810ms |
| 場(chǎng)景3:大文件多文件壓縮(5個(gè)585M) | -- | 173122ms | 53982ms | 50543ms |
| 場(chǎng)景4:100K文件單文件壓縮 | -- | 28ms | 26ms | 24ms |
| 場(chǎng)景5:5K文件單文件壓縮 | 18ms | 20ms | 23ms | |
| 場(chǎng)景5:1K文件單文件壓縮 | 15ms | 21ms | 24ms |
結(jié)論:
- 1)網(wǎng)上流傳的方法不可取,效率最差
- 2)使用緩沖區(qū)雖然性能還湊合,但和兩種nio技術(shù)(通道和mmp)相比,還是差了很多,尤其是在中型文件(500M左右)的單文件壓縮和多文件壓縮
- 中,對(duì)比更加明顯
- 3)通道技術(shù)和mmp技術(shù)對(duì)比相差不大,小型文件基本沒影響,大型文件差距也在幾秒之間
- 4)文件大于10K時(shí),推薦使用通道技術(shù)或者mmp技術(shù)進(jìn)行文件壓縮
- 5)文件小于10K時(shí),推薦使用緩沖區(qū)技術(shù)(比兩種nio技術(shù)表現(xiàn)了更好的性能)
- 6)如果有些團(tuán)隊(duì)在使用api,可以看看其源碼是否使用了nio技術(shù)。如果不是,建議修改為文中方式
另外,操作文件操作時(shí),都可以嘗試使用nio技術(shù),測(cè)試下其效率,理論上應(yīng)該都是很可觀的
背后機(jī)密
1、網(wǎng)上流傳方法
FileInputStream的read方法如下:
/**
* Reads a byte of data from this input stream. This method blocks
* if no input is yet available.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* file is reached.
* @exception IOException if an I/O error occurs.
*/public int read() throws IOException {
return read0();}private native int read0() throws IOException;這是調(diào)用本地方法與原生操作系統(tǒng)進(jìn)行交互,從磁盤中讀取數(shù)據(jù)。每讀取一個(gè)字節(jié)數(shù)據(jù)就調(diào)用一次這個(gè)方法(一次交互很耗時(shí))。
這個(gè)方法還是每次讀取一個(gè)字節(jié),假如文件很大,這個(gè)開銷是巨大的
2、使用緩沖區(qū)
BufferedInputSream read方法如下:
/**
* See
* the general contract of the <code>read</code>
* method of <code>InputStream</code>.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if this input stream has been closed by
* invoking its {@link #close()} method,
* or an I/O error occurs.
* @see java.io.FilterInputStream#in
*/public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;}這樣雖然也是一次讀一個(gè)字節(jié),但不是每次都從底層讀取數(shù)據(jù),而是一次調(diào)用底層系統(tǒng)讀取了最多buf.length個(gè)字節(jié)到buf數(shù)組中,然后從 buf中一次讀一個(gè)字節(jié),減少了頻繁調(diào)用底層接口的開銷。
3、使用通道
在復(fù)制大文件時(shí),F(xiàn)ileChannel復(fù)制文件的速度比BufferedInputStream/BufferedOutputStream復(fù)制文件的速度快了近三分之一,體現(xiàn)出FileChannel的速度優(yōu)勢(shì)。NIO的Channel的結(jié)構(gòu)更加符合操作系統(tǒng)執(zhí)行I/O的方式,所以其速度相比較于傳統(tǒng)的IO而言速度有了顯著的提高。
操作系統(tǒng)能夠直接傳輸字節(jié)從文件系統(tǒng)緩存到目標(biāo)的Channel中,而不需要實(shí)際的copy階段(copy: 從內(nèi)核空間轉(zhuǎn)到用戶空間的一個(gè)過程)
4、使用mmp
內(nèi)存映射文件,是把位于硬盤中的文件看做是程序地址空間中一塊區(qū)域?qū)?yīng)的物理存儲(chǔ)器,文件的數(shù)據(jù)就是這塊區(qū)域內(nèi)存中對(duì)應(yīng)的數(shù)據(jù),讀寫文件中的數(shù)據(jù),直接對(duì)這塊區(qū)域的地址操作,就可以,減少了內(nèi)存復(fù)制的環(huán)節(jié)。所以說,內(nèi)存映射文件比起文件I/O操作,效率要高,而且文件越大,體現(xiàn)出來的差距越大。
到此這篇關(guān)于一文探索Java文件讀寫更高效方式的文章就介紹到這了,更多相關(guān) Java文件讀寫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA核心知識(shí)之ConcurrentHashMap源碼分析
這篇文章主要介紹了JAVA核心知識(shí)之ConcurrentHashMap源碼分析,想了解ConcurrentHashMap的同學(xué)一定要看啊2021-04-04
java開發(fā)Activiti進(jìn)階篇流程實(shí)例詳解
這篇文章主要為大家介紹了java開發(fā)Activiti進(jìn)階篇流程實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
IDEA在Maven項(xiàng)目中使用本地jar包的方法
我們?cè)谀玫脚f項(xiàng)目的時(shí)候,經(jīng)常會(huì)遇到一種情況,就是這個(gè)項(xiàng)目的maven中依賴了一個(gè)本地的jar包,這種情況就需要引入這個(gè)jar包,所以本文給大家介紹了IDEA在Maven項(xiàng)目中使用本地jar包的方法,需要的朋友可以參考下2024-04-04
Java中使用裝飾設(shè)計(jì)模式實(shí)現(xiàn)動(dòng)態(tài)增強(qiáng)對(duì)象功能
裝飾設(shè)計(jì)模式是Java中一種常用的設(shè)計(jì)模式,它通過動(dòng)態(tài)地將功能透明地附加到對(duì)象上,以擴(kuò)展對(duì)象的功能。裝飾設(shè)計(jì)模式主要應(yīng)用于需要?jiǎng)討B(tài)、透明地增強(qiáng)對(duì)象功能的場(chǎng)景。在Java中,裝飾設(shè)計(jì)模式可通過繼承、接口和代理等方式實(shí)現(xiàn)2023-04-04
Java中使用BigDecimal進(jìn)行浮點(diǎn)數(shù)運(yùn)算
這篇文章主要介紹了Java中使用BigDecimal進(jìn)行浮點(diǎn)數(shù)運(yùn)算,需要的朋友可以參考下2014-07-07
詳解Java動(dòng)態(tài)字節(jié)碼技術(shù)
Java字節(jié)碼增強(qiáng)指的是在Java字節(jié)碼生成之后,對(duì)其進(jìn)行修改,增強(qiáng)其功能,可減少冗余代碼,提高性能等。本文將詳細(xì)介紹Java動(dòng)態(tài)字節(jié)碼技術(shù)。2021-05-05

