Java IO

傳統(tǒng)流式IO

傳統(tǒng)的Java IO是流式的IO实檀,從諸如類名InputStreamOutputStream中就可以看出读规。流式IO是單向的,分為輸入和輸出流杉允。在使用輸入流或者輸出流讀寫文件時(shí)邑贴,每次讀寫操作是以字節(jié)為單位席里,我們需要指定讀出或者寫入的大小,中間沒有任何用戶空間的緩存拢驾。例如從文件中讀取4字節(jié)長(zhǎng)度的數(shù)據(jù)奖磁,Java會(huì)創(chuàng)建一個(gè)4字節(jié)長(zhǎng)度的byte數(shù)組,然后通過JNI層經(jīng)由系統(tǒng)調(diào)用read讀文件繁疤,每次讀入一個(gè)字節(jié)的數(shù)據(jù)咖为,將數(shù)據(jù)寫入對(duì)應(yīng)的byte數(shù)組的正確位置。一共需要進(jìn)行4次系統(tǒng)調(diào)用稠腊,因?yàn)槊看挝覀冎荒茏x入一個(gè)字節(jié)躁染。隨著文件讀入的進(jìn)行,我們沒有辦法重新訪問我們已經(jīng)讀入的數(shù)據(jù)架忌,因?yàn)榱魇菃蜗虻耐掏覀儾荒躶eek某個(gè)位置,除非我們自己將這些已經(jīng)讀入的數(shù)據(jù)進(jìn)行了緩存叹放,才能在以后需要時(shí)進(jìn)行訪問备畦。當(dāng)我們寫數(shù)據(jù)的時(shí)候也是如此,我們每次只能寫入一個(gè)字節(jié)的數(shù)據(jù)许昨,寫4個(gè)字節(jié)的數(shù)據(jù)就需要4次系統(tǒng)調(diào)用。系統(tǒng)調(diào)用需要從用戶態(tài)切換到內(nèi)核態(tài)褥赊,然后再切換回來糕档,可想而知,流式的IO的讀寫性能開銷是很大的拌喉。如下是Java中流式IO讀寫的實(shí)現(xiàn)速那,從代碼中我們就可以印證上面的事實(shí)。

//java.io.InputStream#read(byte[], int, int)
public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read(); //每次只讀一個(gè)字節(jié)
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
//java.io.OutputStream#write(byte[], int, int)
public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]); //每次只寫一個(gè)字節(jié)
        }
    }

NIO

JDK 1.4之后尿背,java引入了NIO端仰。NIO以塊為單位進(jìn)行讀寫,而不是以單字節(jié)為單位田藐。Channel在NIO中代表一個(gè)通道荔烧,我們可以操作通道進(jìn)行讀寫,換句話說汽久,通道是雙向的鹤竭。通過NIO讀寫文件,我們并不是直接操作通道景醇,而是通過Buffer來中轉(zhuǎn)臀稚。Buffer代表一塊緩沖區(qū),其實(shí)就是一個(gè)字節(jié)數(shù)組三痰。當(dāng)我們要寫文件時(shí)吧寺,首先將數(shù)據(jù)寫入對(duì)應(yīng)的buffer中窜管,然后通過channel將buffer中的數(shù)據(jù)寫入文件。而當(dāng)我們需要讀入數(shù)據(jù)時(shí)稚机,也是首先將數(shù)據(jù)讀入一個(gè)Buffer中幕帆,然后從buffer中訪問。因?yàn)槊看尾僮魇且詨K為單位的抒钱,因此我們能大大減少系統(tǒng)調(diào)用的次數(shù)蜓肆,極大的提高IO性能。同時(shí)Buffer作為一個(gè)緩沖區(qū)也允許我們?cè)谥蟮哪扯螘r(shí)間內(nèi)重新訪問之前的數(shù)據(jù)谋币,Buffer內(nèi)部會(huì)自己維護(hù)數(shù)據(jù)的位置信息仗扬,如positionlimitcapacity等蕾额。

DirectByteBuffer vs HeapByteBuffer

ByteBuffer代表一個(gè)字節(jié)數(shù)組的緩沖區(qū)早芭。Java提供了direct和non-direct buffer。java.nio.ByteBuffer#allocate會(huì)創(chuàng)建一個(gè)HeapByteBuffer诅蝶,即分配在jvm heap上的一個(gè)字節(jié)數(shù)組退个。而通過java.nio.ByteBuffer#allocateDirect方法返回一個(gè)DirectByteBuffer對(duì)象,它也是封裝了一個(gè)字節(jié)數(shù)組调炬,但是這個(gè)字節(jié)數(shù)組并不是直接分配在通用的jvm heap上的语盈,而是另外一塊單獨(dú)的內(nèi)存區(qū)域中(人們喜歡將之稱為堆外內(nèi)存),在不同的虛擬機(jī)版本可能有不同的實(shí)現(xiàn)缰泡。例如ART運(yùn)行時(shí)刀荒,會(huì)有一個(gè)heap之外的區(qū)域,我理解為大對(duì)象區(qū)域棘钞,這個(gè)區(qū)域主要用來分配一些大對(duì)象缠借,如Bitmap,DirectByteeBuffer等宜猜。我們都知道大對(duì)象對(duì)jvm的GC會(huì)造成一些影響泼返,所以單獨(dú)開辟這些區(qū)域用來存儲(chǔ)一些生命周期長(zhǎng)的大對(duì)象是有道理,可以減少正常GC的次數(shù)姨拥,提高內(nèi)存效率绅喉。
在進(jìn)行NIO時(shí),我們可以通過DirectByteBuffer提高IO性能垫毙。官方的原話是:

A byte buffer is either <i>direct</i> or <i>non-direct</i>. Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations.

到底是怎么個(gè)性能提高法呢霹疫?還是看代碼更清晰。

//IOUtil.java 
static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
        if(var1 instanceof DirectBuffer) {
            return writeFromNativeBuffer(var0, var1, var2, var4); //如果是directbytebuffer综芥,直接寫
        } else {
            int var5 = var1.position();
            int var6 = var1.limit();

            assert var5 <= var6;

            int var7 = var5 <= var6?var6 - var5:0;
            ByteBuffer var8 = Util.getTemporaryDirectBuffer(var7); //獲取一個(gè)臨時(shí)的directbytebuffer

            int var10;
            try {
                var8.put(var1); //復(fù)制數(shù)據(jù)到directbytebuffer之后再寫
                var8.flip();
                var1.position(var5);
                int var9 = writeFromNativeBuffer(var0, var8, var2, var4);
                if(var9 > 0) {
                    var1.position(var5 + var9);
                }

                var10 = var9;
            } finally {
                Util.offerFirstTemporaryDirectBuffer(var8);
            }

            return var10;
        }
    }

    static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
        if(var1.isReadOnly()) {
            throw new IllegalArgumentException("Read-only buffer");
        } else if(var1 instanceof DirectBuffer) { //是directbytebuffer 直接讀入
            return readIntoNativeBuffer(var0, var1, var2, var4);
        } else {
            ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining()); //獲取一個(gè)臨時(shí)的directbytebuffer

            int var7;
            try {
                int var6 = readIntoNativeBuffer(var0, var5, var2, var4); //讀入數(shù)據(jù)到directbytebuffer中
                var5.flip();
                if(var6 > 0) {
                    var1.put(var5); //拷貝數(shù)據(jù)到目標(biāo)buffer中
                }

                var7 = var6;
            } finally {
                Util.offerFirstTemporaryDirectBuffer(var5);
            }

            return var7;
        }
    }

在使用NIO進(jìn)行讀寫的時(shí)候丽蝎,最終會(huì)調(diào)用IOUtil中的相關(guān)read、write方法⊥雷瑁可以看到如果是DirectByteBuffer红省,在IO時(shí)直接在該buffer上進(jìn)行讀寫。如果不是国觉,則需要獲取一個(gè)臨時(shí)的DirectByteBuffer(jvm從directbytebuffer cache中獲取)吧恃,將數(shù)據(jù)拷貝到directbytebuffer中再寫入或者讀入directbuffer中在拷貝到目標(biāo)Buffer中÷榫鳎可以看到痕寓,如果是DirectByteBuffer,那么可以省去了很多拷貝的開銷蝇闭。那么jvm為什么需要一個(gè)中間的DirectByteBuffer緩沖區(qū)呢呻率?我的猜想是普通的buffer是分配在heap上的,可能是內(nèi)存空間不連續(xù)的字節(jié)數(shù)組呻引,而且隨著程序的運(yùn)行 GC可能會(huì)移動(dòng)對(duì)應(yīng)的字節(jié)數(shù)組礼仗,這就給IO帶來了挑戰(zhàn)。反觀DirectByteBuffer逻悠,它是連續(xù)的字節(jié)數(shù)組元践,不是分配在堆上的,受GC影響小童谒,而且一般而言DirectByteBuffer分配內(nèi)存都是指定non-movale的单旁。但是DirectByteBuffer也不是沒有任何缺點(diǎn),因?yàn)樗皇窃诙焉系募⒁粒钥赡茉斐稍L問速度慢慎恒,并且DirectByteBuffer的分配和釋放開銷比HeapByteBuffer要大。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末撵渡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子死嗦,更是在濱河造成了極大的恐慌趋距,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件越除,死亡現(xiàn)場(chǎng)離奇詭異节腐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)摘盆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門翼雀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人孩擂,你說我怎么就攤上這事狼渊。” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵狈邑,是天一觀的道長(zhǎng)城须。 經(jīng)常有香客問我,道長(zhǎng)米苹,這世上最難降的妖魔是什么糕伐? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮蘸嘶,結(jié)果婚禮上良瞧,老公的妹妹穿的比我還像新娘。我一直安慰自己训唱,他們只是感情好褥蚯,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雪情,像睡著了一般遵岩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巡通,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天尘执,我揣著相機(jī)與錄音,去河邊找鬼宴凉。 笑死誊锭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弥锄。 我是一名探鬼主播丧靡,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼籽暇!你這毒婦竟也來了温治?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤戒悠,失蹤者是張志新(化名)和其女友劉穎熬荆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绸狐,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卤恳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寒矿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片突琳。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖符相,靈堂內(nèi)的尸體忽然破棺而出拆融,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布冠息,位于F島的核電站挪凑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏逛艰。R本人自食惡果不足惜躏碳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望散怖。 院中可真熱鬧菇绵,春花似錦、人聲如沸镇眷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽欠动。三九已至酒请,卻和暖如春粥帚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國打工唆途, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匣椰,地道東北人冰啃。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓伍掀,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親萤厅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子橄抹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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

  • 轉(zhuǎn)自JAVA IO 以及 NIO 理解 一段話總結(jié):傳統(tǒng)io中從磁盤中中讀文件,并把文件通過網(wǎng)絡(luò)(socket)發(fā)...
    抓兔子的貓閱讀 1,343評(píng)論 0 4
  • tags:io categories:總結(jié) date: 2017-03-28 22:49:50 不僅僅在JAVA領(lǐng)...
    行徑行閱讀 2,181評(píng)論 0 3
  • 概覽 IO是Java中的最重要的一個(gè)部分. 其中, java.io是所有編程者都應(yīng)該掌握的IO方式. 在Java ...
    斜陽一樹待鴉歸閱讀 11,814評(píng)論 3 28
  • 上篇說了最基礎(chǔ)的五種IO模型惕味,相信大家對(duì)IO相關(guān)的概念應(yīng)該有了一定的了解楼誓,這篇文章主要講講基于多路復(fù)用IO的Jav...
    三分青年閱讀 426評(píng)論 0 1
  • 由于Netty,了解了一些異步IO的知識(shí)名挥,JAVA里面NIO就是原來的IO的一個(gè)補(bǔ)充慌随,本文主要記錄下在JAVA中I...
    騷的掉渣閱讀 697評(píng)論 0 8