Java I/O回顧

最近在學(xué)習(xí)netty扎拣,其中有對(duì)比bio、nio稽鞭、netty使用上的不同鸟整,也趁此機(jī)會(huì)回顧了相關(guān)知識(shí),加深下理解朦蕴,主要涉及的有FileInputStream篮条、FileOutputStream弟头、BufferedInputStream、BufferedOutputStream涉茧、FileChannel赴恨、MappedByteBuffer等知識(shí)點(diǎn)。

一降瞳、FileIn(Out)putStream(單字節(jié))

public static void fileCopy(String src,String dest) throws IOException{
    long t = System.currentTimeMillis();
    File file = new File(src);
    try(
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(new File(dest));
    ){
        int result = 0;
        while((result = fis.read()) != -1){
            fos.write(result);
        }
    }
    System.err.println("file copy use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}

控制臺(tái)輸出:
file copy use time=62065 file.size=20MB
從輸出接口可以看到嘱支,20MB文件copy用時(shí)一分鐘多,可見(jiàn)不使用緩沖區(qū)挣饥,單字節(jié)讀寫(xiě)時(shí)執(zhí)行慢的無(wú)法接受。

這里使用了try-with-resources的方式來(lái)進(jìn)行流的自動(dòng)關(guān)閉沛膳,對(duì)該知識(shí)點(diǎn)不熟悉的可參考Java 7中的Try-with-resources;

二扔枫、FileIn(Out)putStream(字節(jié)數(shù)組)

public static void fileCopyWithBuffer(String src,String dest) throws IOException{
    long t = System.currentTimeMillis();
    File file = new File(src);
    try(
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(new File(dest));
    ){
        byte[] buffer = new byte[1024];
        int byteRead = 0;
        while((byteRead = fis.read(buffer)) != -1){
            fos.write(buffer,0,byteRead);
        }
    }
    System.err.println("fileCopyWithBuffer use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}

控制臺(tái)輸出:
fileCopyWithBuffer use time=140 file.size=20MB
可見(jiàn)使用buffer進(jìn)行批量讀寫(xiě)后,性能有了質(zhì)的提高.

下面我們來(lái)調(diào)整buffer數(shù)組的長(zhǎng)度為10240锹安,看下輸出:
fileCopyWithBuffer use time=45 file.size=20MB
經(jīng)過(guò)多次嘗試短荐,發(fā)現(xiàn)適當(dāng)?shù)脑黾觔uffer的長(zhǎng)度,可以明顯提升處理速度叹哭。

三忍宋、BufferedIn(Out)putStream(單字節(jié))

public static void fileCopyWithBufferStream(String src,String dest) throws IOException{
    long t = System.currentTimeMillis();
    File file = new File(src);
    try(
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(new File(dest));
        
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
    ){
        int result = 0;
        while((result =bis.read()) != -1){
            bos.write(result);
        }
    }
    System.err.println("fileCopyWithBufferStream use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}

控制臺(tái)輸出:
fileCopyWithBufferStream use time=1199 file.size=20MB
相對(duì)于單字節(jié)的流讀寫(xiě)方式,處理時(shí)間有62s提升至1.2s风罩,提升還是很大的糠排。

BufferedInputStream創(chuàng)建時(shí)內(nèi)部會(huì)默認(rèn)創(chuàng)建一個(gè)長(zhǎng)度8192的byte數(shù)組,read方法實(shí)際上是在這個(gè)內(nèi)存數(shù)組里面讀取數(shù)據(jù)超升,下面我們看下read方法的源碼:

public synchronized int read() throws IOException {
    if (pos >= count) { // 當(dāng)數(shù)據(jù)已讀完時(shí)
        fill(); // 調(diào)用上層inputStream內(nèi)read方法讀取數(shù)據(jù)填充buf
        if (pos >= count) // 如果仍無(wú)可讀數(shù)據(jù)入宦,說(shuō)明數(shù)據(jù)已讀完
            return -1;
    }
    //從此處可看到read操作實(shí)際讀取的是buf數(shù)組的數(shù)據(jù)
    //& 0xff的目的是消除后八位之前的位數(shù)據(jù),保證讀出的字節(jié)數(shù)據(jù)無(wú)變化室琢,防止首位為1的情況轉(zhuǎn)為int時(shí)高位為填充為1
    return getBufIfOpen()[pos++] & 0xff; 
}
  • 當(dāng)緩存區(qū)無(wú)可讀數(shù)據(jù)時(shí)乾闰,調(diào)用fill方法,fill方法內(nèi)部會(huì)考慮mark和reset的邏輯盈滴,設(shè)置新讀入數(shù)據(jù)再數(shù)組內(nèi)的存放位置
  • 調(diào)用InputStream內(nèi)的read(byte b[], int off, int len)方法進(jìn)行數(shù)據(jù)的“批量”讀妊募纭;
  • 調(diào)用bis.read()時(shí)巢钓,從buffer內(nèi)返回?cái)?shù)據(jù)

“批量”的讀數(shù)據(jù)病苗,從而提高了整體的執(zhí)行速度。fill()方法的實(shí)現(xiàn)此處不再展開(kāi)竿报,可參考BufferedInputStream源碼分析.

BufferedOutputStream的write方法铅乡,也是先將數(shù)據(jù)存入buf,當(dāng)buf存滿(mǎn)時(shí)烈菌,將數(shù)據(jù)刷出阵幸;

public synchronized void write(int b) throws IOException {
    if (count >= buf.length) {
        flushBuffer();
    }
    buf[count++] = (byte)b;
}

四花履、BufferedIn(Out)putStream(多字節(jié))

public static void fileCopyWithBufferStreamAndBuffer(String src,String dest) throws IOException{
    long t = System.currentTimeMillis();
    File file = new File(src);
    try(
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(new File(dest));
        
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
    ){
        byte[] buffer = new byte[1024];
        int byteRead = 0;
        while((byteRead = bis.read(buffer)) != -1){
            bos.write(buffer,0,byteRead);// 1
            //bos.write(buffer); // 2
            // 1和2的區(qū)別是,最后一次讀取的數(shù)據(jù)可能不能放滿(mǎn)buffer挚赊,那樣上一次留存的數(shù)據(jù)就會(huì)
            // 被一起寫(xiě)出诡壁,可以通過(guò)定義一個(gè)15長(zhǎng)度的byte數(shù)組,寫(xiě)出時(shí)使用長(zhǎng)度10的byte數(shù)組荠割,就能看到結(jié)果了
        }
    }
    System.err.println("fileCopyWithBufferStreamAndBuffer use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}

控制臺(tái)輸出:
fileCopyWithBufferStreamAndBuffer use time=99 file.size=20MB
可見(jiàn)使用buffer進(jìn)行批量讀寫(xiě)后妹卿,性能有了質(zhì)的提高.

下面我們來(lái)調(diào)整buffer數(shù)組的長(zhǎng)度為10240,看下輸出:
fileCopyWithBufferStreamAndBuffer use time=46 file.size=20MB
經(jīng)過(guò)多次嘗試蔑鹦,發(fā)現(xiàn)適當(dāng)?shù)脑黾觔uffer的長(zhǎng)度夺克,可以明顯提升處理速度。

五嚎朽、FileChannel

public static void fileCopyWithChannel(String src,String dest) throws IOException{
    long t = System.currentTimeMillis();
    File file = new File(src);
    try(
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(new File(dest));
        
        FileChannel in = fis.getChannel();
        FileChannel out = fos.getChannel();
    ){
        in.transferTo(0, in.size(), out);
    }
    System.err.println("fileCopyWithChannel use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}

控制臺(tái)輸出:
fileCopyWithChannel use time=85 file.size=20MB

從輸出結(jié)果看铺纽,單純的copy文件上,使用Channel并無(wú)明顯的性能優(yōu)勢(shì)哟忍。
ps:transferTo方法比較耗費(fèi)內(nèi)存資源狡门,建議只在小文件或小使用量時(shí)使用,避免出現(xiàn)資源不足等異常锅很。

NIO的關(guān)鍵點(diǎn)是通道和緩沖區(qū)其馏,這里不再過(guò)多展開(kāi),NIO入門(mén)推薦Java NIO 系列教程爆安。

六叛复、FileChannel(ByteBuffer)

public static void nioBufferCopy(String src,String dest) throws IOException {  
    long t = System.currentTimeMillis();
    File file = new File(src);
    try(
            FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(new File(dest));
        
        FileChannel in = fis.getChannel();
        FileChannel out = fos.getChannel();
        ){
        ByteBuffer buffer = ByteBuffer.allocate(4096);  
        while (in.read(buffer) != -1) {  
            buffer.flip();  
            out.write(buffer);  
            buffer.clear();  
        }  
    } 
    
    System.err.println("nioBufferCopy use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}  

控制臺(tái)輸出:
nioBufferCopy use time=182 file.size=20MB

下面我們來(lái)調(diào)整buffer數(shù)組的長(zhǎng)度為40960,看下輸出:
nioBufferCopy use time=60 file.size=20MB

七鹏控、FileChannel(MappedByteBuffer)

public static void nioMappedByteBufferCopy(String src,String dest) throws IOException {  
    long t = System.currentTimeMillis();
    File file = new File(src);
    try(
            FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(new File(dest));
        
        FileChannel in = fis.getChannel();
        FileChannel out = fos.getChannel();
        ){  
        MappedByteBuffer  mappedByteBuffer = in.map(MapMode.READ_ONLY, 0, in.size());
        out.write(mappedByteBuffer);  
    } 
    
    System.err.println("nioMappedByteBufferCopy use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
} 

控制臺(tái)輸出:
nioMappedByteBufferCopy use time=50 file.size=20MB
使用內(nèi)存映射文件的方式速度還是比較理想的致扯,更多內(nèi)容推薦閱讀深入淺出MappedByteBuffer,另外,推薦關(guān)注占小狼当辐,他寫(xiě)的精品文章很多抖僵!

八、Buffered(Reader)Writer

字符流的使用相對(duì)簡(jiǎn)單缘揪,這里給出一個(gè)示例代碼耍群。

public static void bufferReaderWriterTest() throws IOException{
    List<String> list = new ArrayList<String>(20);
    for(int i=0;i<20;i++){
        list.add(""+i);
    }
    File file = new File("brwtest.txt");
    try(
            
        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
        BufferedReader br = new BufferedReader(new FileReader(file));
    ){
        for (String item : list) {
            bw.write(item);
            bw.newLine();
        }
        //此處不調(diào)用flush方法,下邊讀取時(shí)內(nèi)容會(huì)為空
        bw.flush();
        
        String line = null;
        while ((line = br.readLine()) != null) {
            System.err.println(line);
        }
        
        //org.apache.commons.io.IOUtils找筝,有許多工具包內(nèi)提供了IO操作方法蹈垢,可以?xún)?yōu)先考慮使用
//          List<String> lines = IOUtils.readLines(br);
//          for (String string : lines) {
//              System.err.println(string);
//          }
    }
}

小結(jié)

我個(gè)人寫(xiě)這篇文章或者說(shuō)寫(xiě)這些demo代碼的收獲有:

  1. 自以為很熟悉的I/O類(lèi),動(dòng)起手來(lái)才發(fā)現(xiàn)自己離“順手拈來(lái)”還有一段不小的差距袖裕;
  2. 從源碼層面了解不同流的實(shí)現(xiàn)方式曹抬,更深一層的提高了自己對(duì)java I/O知識(shí)的理解;
  3. 發(fā)現(xiàn)了過(guò)往所學(xué)知識(shí)的弱點(diǎn)急鳄,看別人博客進(jìn)行學(xué)習(xí)谤民,大致知其然堰酿,未系統(tǒng)梳理,未動(dòng)手練習(xí)张足,未理解其實(shí)現(xiàn)方式和原理触创,浮于表面,隨著時(shí)間的流逝为牍,這些知識(shí)成了零散模糊的片段哼绑。

I/O是java中基礎(chǔ)的知識(shí)點(diǎn),你不妨也閉起眼思考下你能使用多少種不同的方式實(shí)現(xiàn)文件復(fù)制碉咆,或許你會(huì)有不同的發(fā)現(xiàn)抖韩!

ps:本機(jī)mac air,測(cè)試代碼有其隨意性疫铜,copy速度僅能體現(xiàn)量級(jí)上的趨勢(shì)帽蝶,以供參考。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末块攒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子佃乘,更是在濱河造成了極大的恐慌囱井,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趣避,死亡現(xiàn)場(chǎng)離奇詭異庞呕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)程帕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)住练,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人愁拭,你說(shuō)我怎么就攤上這事讲逛。” “怎么了岭埠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵盏混,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我惜论,道長(zhǎng)许赃,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任馆类,我火速辦了婚禮混聊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乾巧。我一直安慰自己句喜,他們只是感情好预愤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著藤滥,像睡著了一般鳖粟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拙绊,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天向图,我揣著相機(jī)與錄音,去河邊找鬼标沪。 笑死榄攀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的金句。 我是一名探鬼主播檩赢,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼违寞!你這毒婦竟也來(lái)了贞瞒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤趁曼,失蹤者是張志新(化名)和其女友劉穎军浆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體挡闰,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乒融,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了摄悯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赞季。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖奢驯,靈堂內(nèi)的尸體忽然破棺而出申钩,到底是詐尸還是另有隱情,我是刑警寧澤叨橱,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布典蜕,位于F島的核電站,受9級(jí)特大地震影響罗洗,放射性物質(zhì)發(fā)生泄漏愉舔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一伙菜、第九天 我趴在偏房一處隱蔽的房頂上張望轩缤。 院中可真熱鬧,春花似錦、人聲如沸火的。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)馏鹤。三九已至征椒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間湃累,已是汗流浹背勃救。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留治力,地道東北人蒙秒。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宵统,于是被迫代替她去往敵國(guó)和親晕讲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理马澈,服務(wù)發(fā)現(xiàn)瓢省,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • (轉(zhuǎn)載說(shuō)明:本文非原創(chuàng)痊班,轉(zhuǎn)載自http://ifeve.com/java-nio-all/) Java NIO: ...
    數(shù)獨(dú)題閱讀 800評(píng)論 0 3
  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API净捅,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,536評(píng)論 1 143
  • 簡(jiǎn)介 Java NIO 是由 Java 1.4 引進(jìn)的異步 IO.Java NIO 由以下幾個(gè)核心部分組成: Ch...
    永順閱讀 1,786評(píng)論 0 15
  • 說(shuō)到這個(gè)話(huà)題,可能很多人會(huì)說(shuō)太不吉利了辩块,這么年輕說(shuō)這個(gè)干嗎【S溃可是我想說(shuō)废亭,這是個(gè)值得深思的問(wèn)題。人總有一死具钥,平和...
    懶蟲(chóng)子的美麗人生閱讀 202評(píng)論 0 0