系統(tǒng)學(xué)習(xí) Java IO (一)----輸入流和輸出流 InputStream/OutputStream

目錄:系統(tǒng)學(xué)習(xí) Java IO ---- 目錄漓库,概覽

InputStream

是Java IO API中所有輸入流的父類海雪。
表示有序的字節(jié)流菊匿,換句話說城丧,可以將 InputStream 中的數(shù)據(jù)作為有序的字節(jié)序列讀取影锈。
這在從文件讀取數(shù)據(jù)或通過網(wǎng)絡(luò)接收時非常有用芹务。
InputStream 通常連接到某些數(shù)據(jù)源,如文件鸭廷,網(wǎng)絡(luò)連接枣抱,管道等
看如下代碼片段:

public class InputStreamExample {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = new FileInputStream("D:\\out.txt");
        //do something with data...
        int data = inputStream.read();
        while (data != -1) {
            System.out.print((char) data);
            data = inputStream.read();
        }
        inputStream.close();
    }
}

注意:為了代碼清晰,這里并沒有考慮處理異常的情況辆床,IO 異常處理有專門的介紹佳晶。

read()

此方法返回的是 int 值,其中包含讀取的字節(jié)的字節(jié)值,可以將返回的 int 強(qiáng)制轉(zhuǎn)換為 char 輸出讼载。
如果 read() 方法返回 -1 轿秧,則表示已到達(dá)流的末尾中跌,這意味著在 InputStream 中不再有要讀取的數(shù)據(jù)。
也就是說淤刃,-1 作為 int 值晒他,而不是 -1 作為 char 或 short,這里有區(qū)別逸贾!

InputStream 類還包含兩個 read() 方法陨仅,這些方法可以將 InputStream 源中的數(shù)據(jù)讀入字節(jié)數(shù)組。
這些方法是:

  • int read(byte[]);
  • int read(byte[], int offset, int length);

一次讀取一個字節(jié)數(shù)比一次讀取一個字節(jié)要快得多铝侵,所以在可以的時候灼伤,使用這些讀取方法而不是 read() 方法。

read(byte [])方法將嘗試將盡可能多的字節(jié)讀入作為參數(shù)給出的字節(jié)數(shù)組咪鲜,因?yàn)閿?shù)組具有空間狐赡。
該方法返回一個 int ,其值是實(shí)際讀取了多少字節(jié)疟丙,這點(diǎn)和 read() 方法不一樣颖侄。
如果可以從 InputStream 讀取的字節(jié)少于字節(jié)數(shù)組的空間,則字節(jié)數(shù)組的其余部分將包含與讀取開始之前相同的數(shù)據(jù)享郊。例如:

InputStream input = new ByteArrayInputStream("123456789".getBytes());
byte[] bytes = new byte[4]; // 每次只讀取 4 個字節(jié)
int data = input.read(bytes);
while (data != -1) {
      System.out.print(new String(bytes));
      data = input.read(bytes);
}

將輸出 123456789678 览祖,而不是預(yù)期的 123456789 !

因?yàn)榈谝淮巫x取進(jìn) bytes 是 1234 炊琉,第二次將是 5678 展蒂,現(xiàn)在只剩下 9 一個數(shù)字了,注意此時 bytes 的值是 5678 苔咪,然后再讀取剩下 1個 9锰悼,不能裝滿 bytes 了,只能覆蓋 bytes的第一個字節(jié)团赏,最后返回的bytes 是 9678箕般。
所以記住檢查返回的 int 以查看實(shí)際讀入字節(jié)數(shù)組的字節(jié)數(shù)。

int read(byte[], int offset, int length);方法和 read(byte [])方法差不多舔清,只是增加了偏移量和指定長度隘世。
和 read() 一樣,都是返回 -1 表示數(shù)據(jù)讀取結(jié)束鸠踪。
使用實(shí)例如下:

InputStream inputstream = new FileInputStream("D://out.txt");
byte[] data = new byte[1024];
int bytesRead = inputstream.read(data);
while(bytesRead != -1) {
  doSomethingWithData(data, bytesRead);
  bytesRead = inputstream.read(data);
}
inputstream.close();

首先丙者,此示例創(chuàng)建一個字節(jié)數(shù)組。
然后它創(chuàng)建一個名為 bytesRead 的 int 變量來保存每次讀取 byte [] 調(diào)用時讀取的字節(jié)數(shù)营密,
并立即分配 bytesRead 從第一次讀取 byte [] 調(diào)用返回的值械媒。

mark() and reset()

InputStream 類有兩個名為 mark() 和 reset() 的方法,InputStream 的子類可能支持也可能不支持:

  1. 該子類覆蓋 markSupported() 并返回true,則支持 mark( )和 reset() 方法纷捞。
  2. 該子類覆蓋 markSupported() 并返回 false 痢虹,則不支持 mark() 和 reset() 。
  3. 該子類不重寫 markSupported() 方法 主儡,則是父類的默認(rèn)實(shí)現(xiàn) public boolean markSupported() { return false; } 也是不支持 mark( )和 reset() 方法

mark() 在 InputStream 內(nèi)部設(shè)置一個標(biāo)記奖唯,默認(rèn)值在位置 0 處。
可以手動標(biāo)記到目前為止已讀取數(shù)據(jù)的流中的點(diǎn)糜值,然后丰捷,代碼可以繼續(xù)從 InputStream 中讀取數(shù)據(jù)。
如果想要返回到設(shè)置標(biāo)記的流中的點(diǎn)寂汇,在 InputStream 上調(diào)用 reset() 病往,然后 InputStream “倒退”并返回標(biāo)記,
如此骄瓣,便可再次從該mark點(diǎn)開始返回(讀韧O铩)數(shù)據(jù)。很明顯這可能會導(dǎo)致一些數(shù)據(jù)從 InputStream 返回多次榕栏。我來舉個例子:

 public static void testMarkAndReset() throws IOException {
        InputStream input = new ByteArrayInputStream("123456789".getBytes());
        System.out.println("第一次打优锨凇:");

        int count = 0;// 計(jì)算是第幾次讀取,將在第二次讀取時做標(biāo)記扒磁;
        byte[] bytes = new byte[3]; // 每次只讀取 3 個字節(jié)
        int data = input.read(bytes);
        while (data != -1) {
            System.out.print(new String(bytes));
            if (++count == 2) { // 在第二輪讀取庆揪,即讀到數(shù)字 4 的時候,做標(biāo)記
                input.mark(16); // 從 mark 點(diǎn)開始再過 readlimit 個字節(jié)渗磅,mark 將失效
            }
            data = input.read(bytes);
        }

        input.reset();
        System.out.println("\n在經(jīng)過 mark 和 reset 之后從 mark 位置開始打印:");
        data = input.read(bytes);
        while (data != -1) {
            System.out.print(new String(bytes));
            data = input.read(bytes);
        }
    }

將會輸出:

第一次打蛹旆谩:
123456789
在經(jīng)過 mark 和 reset 之后從 mark 位置開始打邮加恪:
789  

另外要說明一下 mark(int readlimit) 參數(shù),readlimit 是告訴系統(tǒng)脆贵,過了這個 mark 點(diǎn)之后医清,給本宮記住往后的 readlimit 個字節(jié),因?yàn)榈綍r候 reset 之后卖氨,要從 mark 點(diǎn)開始讀取的会烙;但實(shí)際情況和 jdk 文檔有出入,很多情況下調(diào)用 mark(int readlimit) 方法后,即使讀取超過 readlimit 字節(jié)的數(shù)據(jù)筒捺,mark 標(biāo)記仍有效,這又是為什么呢柏腻?網(wǎng)上有人解答,但我還是決定親自探索一番系吭。

我們這個實(shí)例引用的實(shí)際對象是 ByteArrayInputStream 先看一下它的源碼:

/* Note: The readAheadLimit for this class has no meaning.*/
    public void mark(int readAheadLimit) {
        mark = pos;
    }

好家伙五嫂,它說這個參數(shù)對于這個類沒有任何作用。

注意:這段是源碼分析可看可不看,跳過不影響閱讀

那我們在看看其他的 InputStream 子類沃缘,經(jīng)驗(yàn)證躯枢,F(xiàn)ileInputStream 和一些實(shí)現(xiàn)類不支持 mark() 方法,我們看看 BufferedInputStream類源碼:
我先把一些字段的含義說明一下:

  • count 索引1大于緩沖區(qū)中最后一個有效字節(jié)的索引槐臀。 該值始終在0到buf.length的范圍內(nèi); 元素buf [0]到buf [count-1]包含從底層輸入流獲得的緩沖輸入數(shù)據(jù)锄蹂。在 read() 方法中讀完數(shù)據(jù)返回 -1 就是因?yàn)?code>if (pos >= count) return -1;
  • pos 指緩沖區(qū)中的當(dāng)前位置。 這是要從 buf 數(shù)組中讀取的下一個字符的索引水慨。
    該值始終在 0 到 count 范圍內(nèi)得糜。 如果它小于 count,則 buf [pos] 是要作為輸入提供的下一個字節(jié); 如果它等于 count 讥巡,則下一個讀取或跳過操作將需要從包含的輸入流中讀取更多字節(jié)掀亩。(即重新從輸入流中取出一段數(shù)據(jù)緩存)
  • markpos 是調(diào)用最后一個 mark() 方法時 pos 字段的值。該值始終在-1到pos的范圍內(nèi)欢顷。 如果輸入流中沒有標(biāo)記位置槽棍,則此字段為-1。

BufferedInputStream 是每次讀取一定量的數(shù)據(jù)到 buf 數(shù)組中的抬驴,設(shè)置了 readlimit 肯定是想讓數(shù)組從 mark 索引開始至少記錄到 (mark + readlimit) 索引炼七。

public synchronized void mark(int readlimit) {
        marklimit = readlimit;
        markpos = pos;
    }

public synchronized void reset() throws IOException {
        getBufIfOpen(); // Cause exception if closed
        if (markpos < 0)
            throw new IOException("Resetting to invalid mark");
        pos = markpos;
    }

 private void fill() throws IOException {
        byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;            /* 如果標(biāo)記點(diǎn)不在緩沖數(shù)組里(沒標(biāo)記點(diǎn)),丟掉buffer布持,取新數(shù)據(jù) */
        else if (pos >= buffer.length)  /* 緩沖區(qū)中當(dāng)前位置比buffer數(shù)組大豌拙,才執(zhí)行下面代碼 */
            if (markpos > 0) {  /* 可以把 markpos 左邊的數(shù)據(jù)丟掉 */
                int sz = pos - markpos; // 需要緩存的字節(jié)長度,從 markpos 開始
                System.arraycopy(buffer, markpos, buffer, 0, sz); // 復(fù)用內(nèi)存空間
                pos = sz;
                markpos = 0;
            } else if (buffer.length >= marklimit) { // 如果 buffer 的長度已經(jīng)大于 marklimit
                markpos = -1;   /* 那 mark 就失效了*/
                pos = 0;        /* 刪除buffer內(nèi)容题暖,取新數(shù)據(jù) */
            } else if (buffer.length >= MAX_BUFFER_SIZE) { // 如果buffer過長就拋錯
                throw new OutOfMemoryError("Required array size too large");
            } else {            /* buffer 還沒 marklimit 大按傅,擴(kuò)容到 pos 的2倍或者最大值 */
                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                        pos * 2 : MAX_BUFFER_SIZE;
                if (nsz > marklimit)
                    nsz = marklimit;
                byte nbuf[] = new byte[nsz];
                System.arraycopy(buffer, 0, nbuf, 0, pos);
                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

可以得出:設(shè)置標(biāo)記后,

  1. 如果緩沖區(qū)中當(dāng)前位置比 buffer 數(shù)組小胧卤,也就是還沒讀完 buffer 數(shù)組交汤,那 mark 標(biāo)記不會失效拓瞪;
  2. 下次繼續(xù)讀取檐春,超過 buffer 大小字節(jié)后唠椭,判斷 markpos 是否大于0,如果 markpos 大于0叶撒,即還在 buffer 數(shù)組內(nèi)绝骚,則把 markpos 左邊的數(shù)據(jù)清除,markpos 指向 0 , 復(fù)用內(nèi)存空間祠够,并設(shè)置 buffer 的大小為 (pos - markpos) 的值压汪;
  3. 再繼續(xù)讀取,此時 markpos 肯定不在 buffer 數(shù)組包含范圍了古瓤,此時判斷 buffer 的長度是否大于等于
    marklimit 蛾魄,如果小于 marklimit ,那說明設(shè)置 mark 后讀取的數(shù)據(jù)長度還沒達(dá)到要求的 marklimit 了,給我繼續(xù)滴须,保持從 mark 點(diǎn)開始緩存, mark 標(biāo)記不會失效舌狗。然后 buffer 就擴(kuò)容到Math.min(2倍 pos 或最大值 ,marklimit)扔水;
  4. 再繼續(xù)讀取痛侍,同上,buffer 這么努力擴(kuò)容魔市,總有大于 marklimit 的時候主届,這時說明設(shè)置 mark 后繼續(xù)讀取的數(shù)據(jù)長度已經(jīng)超過要求的 marklimit 了,仁盡義至待德,標(biāo)記失效君丁;

我們就只分析了 ByteArrayInputStream 和 BufferedInputSteam 類的算法,其它輸入流不知道将宪。因此 mark() 方法標(biāo)記時绘闷,務(wù)必考慮好 readlimit 的值。

OutputStream

OutputStream 通常始終連接到某個數(shù)據(jù)目標(biāo)较坛,如文件印蔗,網(wǎng)絡(luò)連接,管道等丑勤。 OutputStream 的目標(biāo)是將數(shù)據(jù)寫入到外部华嘹。

write(byte)

write(byte) 方法用于將單個字節(jié)寫入 OutputStream。 OutputStream 的 write() 方法接受一個 int 法竞,其中包含要寫入的字節(jié)的字節(jié)值耙厚。 只寫入 int 值的第一個字節(jié)。 其余的被忽略了岔霸。
OutputStream 的子類有重寫的 write() 方法薛躬。 例如,DataOutputStream 允許使用相應(yīng)的方法writeBoolean()秉剑,writeDouble() 等編寫諸如 int泛豪,long稠诲,float侦鹏,double,boolean 等 Java 基本類型臀叙。

write(byte[] bytes) 略水, write(byte[] bytes, int offset, int length)

和 InputStream 一樣,它們也可以將一個數(shù)組或一部分字節(jié)寫入 OutputStream 劝萤。

flush()

OutputStream 的flush() 方法將寫入 OutputStream 的所有數(shù)據(jù)刷新到底層數(shù)據(jù)目標(biāo)渊涝。 例如,如果 OutputStream 是 FileOutputStream ,則寫入 FileOutputStream 的字節(jié)可能尚未完全寫入磁盤跨释。 即使您的代碼已將其寫入 FileOutputStream 胸私,但數(shù)據(jù)也可能還在某處緩存在內(nèi)存中。 通過調(diào)用 flush() 可以確保將任何緩沖的數(shù)據(jù)刷新(寫入)到磁盤(或網(wǎng)絡(luò)鳖谈,或 OutputStream 的目標(biāo))岁疼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缆娃,隨后出現(xiàn)的幾起案子捷绒,更是在濱河造成了極大的恐慌,老刑警劉巖贯要,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暖侨,死亡現(xiàn)場離奇詭異,居然都是意外死亡崇渗,警方通過查閱死者的電腦和手機(jī)字逗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來显押,“玉大人扳肛,你說我怎么就攤上這事〕吮” “怎么了挖息?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長兽肤。 經(jīng)常有香客問我套腹,道長,這世上最難降的妖魔是什么资铡? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任电禀,我火速辦了婚禮,結(jié)果婚禮上笤休,老公的妹妹穿的比我還像新娘尖飞。我一直安慰自己,他們只是感情好店雅,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布政基。 她就那樣靜靜地躺著,像睡著了一般闹啦。 火紅的嫁衣襯著肌膚如雪沮明。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天窍奋,我揣著相機(jī)與錄音荐健,去河邊找鬼酱畅。 笑死,一個胖子當(dāng)著我的面吹牛江场,可吹牛的內(nèi)容都是我干的纺酸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼址否,長吁一口氣:“原來是場噩夢啊……” “哼吁峻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起在张,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤用含,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后帮匾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啄骇,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年瘟斜,在試婚紗的時候發(fā)現(xiàn)自己被綠了缸夹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡螺句,死狀恐怖虽惭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛇尚,我是刑警寧澤芽唇,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站取劫,受9級特大地震影響匆笤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谱邪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一炮捧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惦银,春花似錦咆课、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蘸吓,卻和暖如春善炫,著一層夾襖步出監(jiān)牢的瞬間撩幽,已是汗流浹背库继。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工箩艺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宪萄。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓艺谆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拜英。 傳聞我的和親對象是個殘疾皇子静汤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評論 2 348

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