【Java中級】1.0 Java核心之IO流(一)——生猛理解字節(jié)流

1.0 為什么要寫這個。
  • 在當(dāng)初學(xué)java語言的時候,其實感覺這算是最難的基礎(chǔ)部分內(nèi)容之一,因為字符流和字節(jié)流的存在,還有緩沖字節(jié)流、字符流,選擇太多,不同的限制,導(dǎo)致使用的時候根本不知道:
1. 到底什么情況下怎么寫
2. 什么方案和代碼,寫會沒有什么大的問題。
  • 所以在這里寫下相關(guān)知識點,所謂授人以漁,更要授人以 鱗、 鯉、 鯽、 鯨、 鰭、 鰲、 鰓、 鱖、 鱸、 鮭、 鯀、 鯤、 鯡、 鯫、 鯢、 鮒、 鱒、 鳒、 鰣、 鲝、 鲹、 鯖、 鲉、 鰈、 鳀、 鮐、 鯁、 鳑、 鳛、 鲞、 鲬、 鰉、 鰱、 鯪、 鰩、 鮪……
3.本來一個內(nèi)容就打算寫一篇的,簡書說我寫得太長了,不許我發(fā)布,所以拆成兩篇,查閱本篇的朋友請結(jié)合另一篇一同參考,謝謝。

鏈接如下:
【Java】2.0 Java核心之IO流(二)——生猛理解字符流

2.0 概念
  • 2.1 IO流用來處理設(shè)備之間的數(shù)據(jù)傳輸
  • 2.2 Java對數(shù)據(jù)的操作是通過流的方式
  • 2.3 Java用于操作流的類都在IO包中
  • 2.4流按流向分為兩種:輸入流,輸出流。
  • 2.5流按操作類型分為兩種
    • 字節(jié)流 : 字節(jié)流可以操作任何數(shù)據(jù),因為在計算機中任何數(shù)據(jù)都是以字節(jié)的形式存儲的
    • 字符流 : 字符流只能操作純字符數(shù)據(jù),比較方便。
3.0 IO流常用父類
  • 字節(jié)流的抽象父類:
    • InputStream
    • OutputStream
  • 字符流的抽象父類:
    • Reader
    • Writer
4.0 說到這里 ,你肯定覺得煩,因為上面講地的確都是廢話。
  • 字節(jié)流讀取英文、數(shù)字、視頻、音頻、圖片等超級好用的,速度快,這個是它存在的最大用處。

  • 字符流就是涉及中文輸入,如果用字節(jié)流,就會出現(xiàn)把一個字符,人為地拆開成兩個字節(jié),然后就各種亂碼。

  • 所以,當(dāng)你知道自己需要傳英文、數(shù)字、視頻、音頻、圖片等不涉及中文傳輸?shù)臅r候,用字節(jié)流比較好,規(guī)模小,耗時小,占用資源少,效率高。
    當(dāng)涉及中文傳輸時,那是不是用字符流才是王道,不出錯才是爸爸呢?

    錯! 如果在只讀或著只寫中文的情況下,才建議用用字符流,如果讀就是為了寫(比如復(fù)制,上傳存儲,發(fā)送給對方文件等),那還是用字節(jié)流會比較好(后面會解釋)。
    再重復(fù)一遍,就是除了一種特殊情況,不然還是用字節(jié)流處理就行了。(這種情況是只讀或著只寫

  • 字符流有缺點:不可以拷貝非純文本的文件!
    比如說,用字符流讀取一張圖片,或者某個網(wǎng)址,因為在讀的時候會將字節(jié)轉(zhuǎn)換為字符,在轉(zhuǎn)換過程中,可能找不到對應(yīng)的字符,就會用?代替,寫出的時候會將?字符轉(zhuǎn)換成字節(jié)寫出去,如果是?字符直接寫出,寫出之后的文件就亂了,完全看不了。

5.0 IO程序代碼只有3步

是的,無論你要考慮什么情況,判斷什么條件,發(fā)送什么東西,通通只要2元……
好吧,總共分為4步走:

    1. 使用前,導(dǎo)入IO包中的類
  • 2.使用時,進(jìn)行IO異常處理
  • 3.使用后,釋放資源
  • 4.沒了
6.0 字節(jié)流(7.0是緩沖字節(jié)流,8.0是字符流、9.0是緩沖字符流,END,IO流結(jié)束,本篇結(jié)束)
6.1 下面貼上一段完整的版本的處理方案:
package com.edpeng.stream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo_TryFinally {

    /**
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        //demo1();
        try(
            //這個xxx.txt,自己在工程目錄下新建一個就好,里面自己錄入一些英文字母就可以
            //別錄入中文,早晚會錯
            FileInputStream fis = new FileInputStream("xxx.txt");
            FileOutputStream fos = new FileOutputStream("yyy.txt");
            //下面這行純粹為了測試用,可以沒有的
            MyClose mc = new MyClose();
        ){
            int b;
            //讀一行就寫(復(fù)制)一行,讀寫就是這樣完成的,當(dāng)然你可以拆開,往里面加邏輯。
            while((b = fis.read()) != -1) {
                fos.write(b);
            }
        }
    }

    public static void demo1() throws FileNotFoundException, IOException {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("xxx.txt");
            fos = new FileOutputStream("yyy.txt");
            
            int b;
            while((b = fis.read()) != -1) {
                fos.write(b);
            }
        }finally {
            try{
                if(fis != null)
                    fis.close();
            }finally { //try fianlly的嵌套目的是能關(guān)一個盡量關(guān)一個
                if(fos != null)
                    fos.close();
            }
        }
    }

}

class MyClose implements AutoCloseable {
    public void close() {
        System.out.println("我關(guān)了");
    }
}

6.11 demo1()方法main主函數(shù)里面的方法,是兩套。
demo1()方法主要是java jdk1.6以前版本使用的方法

先解釋demo1()方法,雖然里面的套路我們一般不這么用,但這樣的確是最嚴(yán)謹(jǐn)?shù)膶懛?,常常會在面試中用到,?dāng)然,如果你平時都這么寫,肯定是最好的。
-demo1()方法main主函數(shù)兩個方法的目的都是讀取文件,輸出(復(fù)制)新的文件。
-demo1()方法,如果去掉里面的try/finally,也是可以的,因為異常已經(jīng)在方法那里直接拋出來了,這樣做相當(dāng)于try/finally里面鑲嵌了一套try/finally。
-demo1()方法,之所以這樣做,主要是為了處理3個方面的問題,:

        1. fis和 fos報錯。
        2. read()和write()報錯。
        3.  fis.close();和   fos.close();報錯。

為什么要處理這3種報錯情況?

第1種,可能存在讀取不到指定文件(可能不存在了),或可能存在沒法寫入(指定目錄不存在)。

如果沒有最外層的try/finally,假設(shè)fis = new FileInputStream("xxx.txt");工作正常,fos = new FileOutputStream("yyy.txt");拋異常,問題來了,第2個fis代表的FileInputStream文鍵輸入流沒關(guān)!運行不到“fis.close();”代碼就嗝屁了。

第2種,于是我們有了最外層的try/finally,那么無論try里面怎么拋異常,fis.close();fos.close();都能在finally里面關(guān)掉。

但是try里面還有一個問題,read()和write()拋異常。

有人會說,只要文件存在,讀和寫怎么會拋異常呢,文件里面就算是亂碼,那就亂讀亂寫唄。在windows操作環(huán)境下,基本沒什么大問題,但比如在linux環(huán)境下,文件經(jīng)常存在可讀性、可改性等基本屬性,所以很可能即使fis和 fos不拋異常,read()和write()還是會報錯的。所以這個也需要寫入到try/finally里面。

第3種,finally里面又嵌入了try/finally語句

這是為了解決如果第一個fis.close();拋異常(比如數(shù)據(jù)庫奔潰,服務(wù)器宕機等,沒關(guān)成)的話,至少第二個 fos.close();不會因為第一個直接拋異常而終止程序,導(dǎo)致 fos.close();沒關(guān),好歹至少關(guān)一個是吧。

完畢。

6.12 main主函數(shù),大家不要上去翻代碼了,這里直接貼下來:

public static void main(String[] args) throws IOException {
        try(
            //這個xxx.txt,自己在工程目錄下新建一個就好,里面自己錄入一些英文字母就可以
            //別錄入中文,早晚會錯
            FileInputStream fis = new FileInputStream("xxx.txt");
            FileOutputStream fos = new FileOutputStream("yyy.txt");
            //下面這行純粹為了測試用,可以沒有的
            MyClose mc = new MyClose();
        ){
            int b;
            //讀一行就寫(復(fù)制)一行,讀寫就是這樣完成的,當(dāng)然你可以拆開,往里面加邏輯。
            while((b = fis.read()) != -1) {
                fos.write(b);
            }
        }
    }

    class MyClose implements AutoCloseable {
        public void close() {
            System.out.println("我關(guān)了");
        }
    }

可以看到,代碼簡化了不少,用了一個try( ){ }語句,這個是java jdk1.7之后,可以使用的新玩法。
這個語句命令的意思:無論大括號里面做什么,最后小括號里的東西,都會調(diào)用自己本來就是實現(xiàn)好的接口中的close()方法。

這里面有個MyClose( )方法,這主要為了我們這個例子測試用,平常不要加這個方法。 這個方法實現(xiàn)了接口AutoCloseable。因為我們的 InputStreamOutputStream類其實都實現(xiàn)了這個接口。

不信我們舉個例子,查看FileInputStream類的源代碼:

2019-03-09_011845.png

在eclipse里面按住ctrl鍵,然后挪動鼠標(biāo)放到FileInputStream類上,點擊Open Declaration,這樣我們可以查看源代碼(查看不了的可以想想辦法,百度一下就好了。需要的留言,給教程)。

2019-03-09_012244.png

可以看到,FileInputStream類繼承了InputStream類,接著查看InputStream類源代碼:
2019-03-09_012401.png

可以看到,InputStream類實現(xiàn)了一個Closeable接口,接著查看Closeable接口源代碼:
2019-03-09_012534.png

可以看到,Closeable類繼承了AutoCloseable類,,接著查看AutoCloseable類源代碼:

/*
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */

package java.lang;

/**
 * An object that …… …… non-I/O-based forms.
 *
 * @author Josh Bloch
 * @since 1.7
 */
public interface AutoCloseable {
    /**
     * Closes this resource, relinquishing any …………  if this resource cannot be closed
     */
    void close() throws Exception;
}

這里不適合截圖,直接放上來源代碼,里面大段注釋被我省略號了。

可以看到這個@since 1.7,是java jdk1.7版本之后才實現(xiàn)的接口,里面就一個方法close( )。

其實我們只要某個方法實現(xiàn)了這個AutoCloseable抽象類,就相當(dāng)于擁有了可以自閉 的技能。

我們用一個MyClose( )方法,實現(xiàn)抽象方法AutoCloseable類后,重寫里面的close( )方法,不僅能夠自閉 ,還可以告訴別人它自閉 了。所以在try 語句里面小括號就會自動調(diào)用 InputStream類、OutputStream類和MyClose( )方法中自帶的自閉技能 關(guān)閉自己。

6.2 細(xì)節(jié)來了。

6.21 大家注意到:

              int b;
            //讀一行就寫(復(fù)制)一行,讀寫就是這樣完成的,當(dāng)然你可以拆開,往里面加邏輯。
            while((b = fis.read()) != -1) {
                fos.write(b);
            }

read()方法 返回值為什么是int
首先,上面兩句話是事實,我們下面這么多分析,只是為了理解為什么java要這么設(shè)計。

  • 字節(jié)輸入流可以操作任意類型的文件,比如圖片音頻等,這些文件底層都是以二進(jìn)制形式的存儲的,我們這些文件肯定會有結(jié)束標(biāo)記,而且我們可以去簡單設(shè)計代碼測試一下,最后的返回值是-1。
    所以我們的while語句 里面用讀到最后會是-1 作為判斷條件,表示文件讀完了。

如果是byte 類型,那就是1個字節(jié)(8位)為單位讀取,
就像下面這樣:
00010100 00100100 01000001 11111111 0000000
這樣,byteread() 5次才能全部讀完。但是,當(dāng)我們讀到11111111 的時候,出事了。因為它就是一個byte類型的-1,分析如下:
10000001??byte類型的-1的原碼
11111110??-1的反碼
11111111??-1的補碼
如果每次讀取都返回byte,有可能在讀到中間的時候遇到111111111,那么這11111111是byte類型的-1,我們的程序是遇到-1就會停止不讀了,后面的數(shù)據(jù)就讀不到了。

如果是int 類型,那就是4個字節(jié)為單位讀取,當(dāng)讀到11111111的時候,它會在前面補上3個字節(jié),補上24個0湊足4個字節(jié),變成下面這樣:
00000000 00000000 00000000 11111111 這樣,,這個數(shù)就會變成一個正的255。
這樣可以保證整個數(shù)據(jù)讀完,而結(jié)束標(biāo)記的-1就是int類型
read( )方法 是1個字節(jié)1個字節(jié)地讀,所以每次讀都會去補上24個0湊足4個字節(jié)。

我知道你上面可能還是看不懂,沒關(guān)系,至少可以知道是這么回事,o(╯□╰)o實在沒辦法解釋得更加通俗了。

接著繼續(xù)深入:
上面說了,read( )方法 會給每次讀都會去補上24個0湊足4個字節(jié),那寫的時候豈不是要出錯?
不要擔(dān)心,因為我們其實讀取文件是用read( )方法讀取的,我們的 write( )方法 會在每次寫的時候,自動去掉前面的24個0,一樣會保證數(shù)據(jù)的原樣性。( write( )方法 一次寫出也是1個字節(jié))

FileOutputStream fos = new FileOutputStream("bbb.txt"); //如果沒有bbb.txt,會創(chuàng)建出一個
        //雖然寫出的是一個int數(shù),但是在寫出的時候會將前面的24個0去掉,所以寫出的是一個byte
        fos.write(97);
        fos.write(98);
        fos.write(99);
        fos.close();

這里的結(jié)果是,會在bbb.txt文件里面存入“abc”,解釋如上,所以如果手動寫入,大可不必自己先在想輸入的字節(jié)面前傻乎乎的手動添0。

6.22 如下聲明:

   FileOutputStream fos = new FileOutputStream("yyy.txt");

FileOutputStream輸出流在創(chuàng)建對象的時候,如果沒有這個文件,會幫我們創(chuàng)建出來新的文件,如果有現(xiàn)成的,會把里面的內(nèi)容清空,把新的內(nèi)容寫進(jìn)去。

這里也有一個細(xì)節(jié),有人可能注意到,到底什么時候,哪條語句,執(zhí)行了清空(新建)的命令。
沒錯,就是FileOutputStream fos = new FileOutputStream("yyy.txt");這句。而且是所有的輸出流都是這樣,不只是FileOutputStream字節(jié)輸出流。

重點來了:如果想讀取同一個文件,經(jīng)過一番邏輯處理后把數(shù)據(jù)又存回原文件,千萬不要聲明FileInputStream指向文件,接著聲明FileOutputStream指向同一個文件,這時候因為你的聲明導(dǎo)致那個文件里面的內(nèi)容已經(jīng)被清空了。
舉例說:下面這樣是不行的,運行結(jié)果是null。因為aaa的文件在輸出流聲明之后里面已經(jīng)沒東西了。

public static void demo() throws FileNotFoundException, IOException {
        FileInputStream fis = new FileInputStream("aaa.txt");
        FileOutputStream fos = new FileOutputStream("aaa.txt");
        byte[] arr = new byte[fis.available()];
        fis.read(arr);                        
        fos.write(arr);                                             
        
        fis.close();
        fos.close();
}

下面這樣就沒問題了:

public static void demo() throws FileNotFoundException, IOException {
        FileInputStream fis = new FileInputStream("aaa.txt");
        byte[] arr = new byte[fis.available()];
        fis.read(arr); 
        FileOutputStream fos = new FileOutputStream("aaa.txt");
        fos.write(arr);                                             
        
        fis.close();
        fos.close();
}

這個例子也讓我們體驗到流使用時(對,凡是各種語言中,不僅僅是java語言,只要是涉及流這種定義的),常遵循的一個原則——晚開早關(guān),晚開早關(guān),晚開早關(guān),重要的話說三遍,什么時候用,什么時候再開流,不用了就及時關(guān)掉。


6.23 接著說寫的事情。前面說到:FileOutputStream輸出流在創(chuàng)建對象的時候,如果沒有這個文件,會幫我們創(chuàng)建出來新的文件,如果有現(xiàn)成的,會把里面的內(nèi)容清空,把新的內(nèi)容寫進(jìn)去。
那么如何把里面原有的內(nèi)容不刪掉,直接在后面添加就好?

FileOutputStream fos = new FileOutputStream("bbb.txt",true);    //如果沒有bbb.txt,會創(chuàng)建出一個
        fos.write(97);  
        fos.write(98);
        fos.write(99);
        fos.close();

看懂了沒有,只需要多一個true 。

6.254 拷貝圖片也是這樣:

//創(chuàng)建輸入流對象,讀取,關(guān)聯(lián)致青春.mp3
public static void demo() throws FileNotFoundException, IOException {
        FileInputStream fis = new FileInputStream("狂狼.mp3");    
        //創(chuàng)建輸出流對象,寫出,關(guān)聯(lián)copy.mp3
        FileOutputStream fos = new FileOutputStream("copy.mp3");
        int b;
        while((b = fis.read()) != -1) {
            fos.write(b);
        }
        fis.close();
        fos.close();
}

6.25 字節(jié)流一次讀寫一個字節(jié)復(fù)制音頻、視頻等,效率太低,怎么辦?

// 本方法不推薦使用,因為有可能會導(dǎo)致內(nèi)存溢出
public static void demo() throws FileNotFoundException, IOException {
        //創(chuàng)建輸入流對象,關(guān)聯(lián)狂狼.mp3
        FileInputStream fis = new FileInputStream("狂狼.mp3");
        //創(chuàng)建輸出流對象,關(guān)聯(lián)copy.mp3
        FileOutputStream fos = new FileOutputStream("copy.mp3");
        //創(chuàng)建與文件一樣大小的字節(jié)數(shù)組
        byte[] arr = new byte[fis.available()];
        //將文件上的字節(jié)讀取到內(nèi)存中     
        fis.read(arr);
        //將字節(jié)數(shù)組中的字節(jié)數(shù)據(jù)寫到文件上                                  
        fos.write(arr);                                             
        
        fis.close();
        fos.close();
}

這里用了一個available() 方法,想想也知道這個就是可以查詢得知輸入流文件大小的方法。

為什么不推薦使用呢,因為如果我們讀取例如200MB大小的文件,這個沒什么關(guān)系,如果是一個20GB的壓縮包呢,當(dāng)我們在創(chuàng)建數(shù)組arr的時候,內(nèi)存根本放不下。至少,我們平常見到的電腦,其運行內(nèi)存一般也就4GB、8GB,16GB都算高配了。

優(yōu)化一下,我們可以這樣:

public static void demo() throws FileNotFoundException, IOException {
        FileInputStream fis = new FileInputStream("xxx.txt");
        FileOutputStream fos = new FileOutputStream("yyy.txt");
        
        byte[] arr = new byte[2];
        int len;
        while((len = fis.read(arr)) != -1) {
            fos.write(arr,0,len);
        }
        
        fis.close();
        fos.close();
    }

這里大家可能要說了——又變了,你個渣男,說好的一開始就給的完整版,都等著套模板了,你居然還要變……

這里write( )方法多了個變化,意思是:一次讀取arr長度的字節(jié)內(nèi)容,偏移量為0個字節(jié),讀取len個長度。,從0個字節(jié)的位置開始寫入arr數(shù)組里面的內(nèi)容,從前頭開始數(shù)len長度的個數(shù)。
看不懂具體了解這個方法請移步java中文開發(fā)說明文檔。
總之,這是為了處理最后剩下需要讀取不足arr數(shù)組長度的時候,將末尾會自動補足的0給去掉,保證數(shù)據(jù)的原樣性。

當(dāng)然,我們還有最終優(yōu)化,因為這樣做僅僅比讀取1一個字節(jié)提高了1倍的效率。

                FileInputStream fis = new FileInputStream("狂狼.mp3");
        FileOutputStream fos = new FileOutputStream("copy.mp3");
        
        byte[] arr = new byte[1024 * 8];
        int len;
        while((len = fis.read(arr)) != -1) {                //如果忘記加arr,返回的就不是讀取的字節(jié)個數(shù),而是字節(jié)的碼表值
            fos.write(arr,0,len);
        }
        
        fis.close();
        fos.close();

看到了沒有,這里把arr數(shù)組變成1024的整數(shù)倍,因為計算機的字節(jié)倍數(shù)就是1024的倍數(shù)(比如1KB = 1024B),這樣的效率就很高了,當(dāng)然,你可以定義更好的處理邏輯。

7.0 緩沖字節(jié)流

其實就是在 FileInputStream類和 FileOutputStream類外面包了兩層皮,不信你去查看源碼或者查看繼承關(guān)系。
BufferedInputStream
BufferedOutputStream

7.1 首先說明的是緩沖流的思想

A:緩沖思想

  • 字節(jié)流一次讀寫一個數(shù)組的速度明顯比一次讀寫一個字節(jié)的速度快很多。
  • 這是加入了數(shù)組這樣的緩沖區(qū)效果,java本身在設(shè)計的時候,也考慮到了這樣的設(shè)計思想(裝飾設(shè)計模式,下面會解釋這個設(shè)計模式),所以提供了字節(jié)緩沖區(qū)流

B:BufferedInputStream

  • BufferedInputStream 內(nèi)置了一個緩沖區(qū)(數(shù)組)
  • BufferedInputStream 中讀取一個字節(jié)時
  • BufferedInputStream 會一次性從文件中讀取8192個, 存在緩沖區(qū)中, 返回給程序一個
  • 程序再次讀取時, 就不用找文件了, 直接從緩沖區(qū)中獲取
  • 直到緩沖區(qū)中所有的都被使用過, 才重新從文件中讀取8192個

C:BufferedOutputStream

  • BufferedOutputStream 也內(nèi)置了一個緩沖區(qū)(數(shù)組)
  • 程序向流中寫出字節(jié)時, 不會直接寫到文件, 先寫到緩沖區(qū)中
  • 直到緩沖區(qū)寫滿, BufferedOutputStream 才會把緩沖區(qū)中的數(shù)據(jù)一次性寫到文件里

看懂了沒有,其實緩沖流其實和上面6.25小點里面的自定義數(shù)組是一個尿性。

所以能用緩沖流就用緩沖流,畢竟讀取也快,寫出也快。

7.2 代碼相當(dāng)簡單:
        //創(chuàng)建文件輸入流對象,關(guān)聯(lián)狂狼.mp3
        FileInputStream fis = new FileInputStream("狂狼.mp3");
        //創(chuàng)建緩沖區(qū)對fis裝飾
        BufferedInputStream bis = new BufferedInputStream(fis);
        //創(chuàng)建輸出流對象,關(guān)聯(lián)copy.mp3
        FileOutputStream fos = new FileOutputStream("copy.mp3");
        //創(chuàng)建緩沖區(qū)對fos裝飾
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        int b;
        while((b = bis.read()) != -1) {     
            bos.write(b);
        }
        //只關(guān)裝飾后的對象即可
        bis.close();
        bos.close();

如果你覺得還是多了兩行代碼,明明已經(jīng)變復(fù)雜了,其實我們一般寫成這樣:

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("狂狼.mp3"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.mp3"));
        
        int b;
        while((b = bis.read()) != -1) {
            bos.write(b);
        }
        bis.close();
        bos.close();

這充分證明了我不是渣男。

這里還有一個疑問,那如果我像6.25小點里面的自定義數(shù)組和用帶Buffered的讀取哪個更快?

  • 定義小數(shù)組如果是8192個字節(jié)大小和Buffered比較的話
  • 定義小數(shù)組會略勝一籌,因為讀和寫操作的是同一個數(shù)組
  • 而Buffered操作的是兩個數(shù)組

7.3 細(xì)節(jié)來了。

7.31 我們知道,在這些IO流操作里面,可能大家在學(xué)習(xí)的時候,可能是培訓(xùn)老師,可能是某本書,會告訴你每次記得——最后放進(jìn)去一個flush( )方法,目的是確保緩沖流的所有數(shù)據(jù)都會寫進(jìn)去,別丟數(shù)據(jù)。

但是!其實你不用也沒關(guān)系……只要你有下面的操作就可以了:

        bis.close();
        bos.close();

flush( )方法

  • 用來刷新緩沖區(qū)的,刷新后可以再次寫出

close( )方法

  • 用來關(guān)閉流釋放資源的的,如果是帶緩沖區(qū)的流對象的close()方法,不但會關(guān)閉流,還會再關(guān)閉流之前刷新緩沖區(qū),關(guān)閉后不能再寫出

所以,到底什么時候用flush( )方法?

  • ??如果緩沖流還想接著用,比如聊天軟件,我發(fā)一段話給你,總不能等我湊夠8192個字節(jié)后,再一波發(fā)給對方看。肯定發(fā)一次就清空一次緩沖流。
  • ??但是如果調(diào)用close( )方法,那不好意思,說完這句都沒法再說了,因為流已經(jīng)關(guān)閉了。所以往往是這種情況下就要用flush( )方法,發(fā)出去后,沒關(guān)系,想發(fā)還可以繼續(xù)發(fā),想收的,先把收到的及時清空,顯示給對方看,再繼續(xù)收。


    7.32 字節(jié)流讀取中文的問題
    總有人問,那字節(jié)流就搞不定中文的問題了?不,是真搞不定……
    我們知道1個中文占用1個字符,等于2個字節(jié)。
    字節(jié)流在讀中文的時候有可能會讀到半個中文,造成亂碼,可以用下面的方法測試:
public static void demo() throws FileNotFoundException, IOException {
        FileInputStream fis = new FileInputStream("yyy.txt");
        byte[] arr = new byte[4];
        int len;
        while((len = fis.read(arr)) != -1) {
            System.out.println(new String(arr,0,len));
        }
        
        fis.close();
    }

當(dāng)然,"yyy.txt"里面現(xiàn)在存了一些中文在里面,自己隨便測吧,總之當(dāng)一次讀取4個字節(jié)的時候,剛好斷開點是一個中文的上半個字節(jié)的話,亂碼就來了。

7.33 字節(jié)流寫出中文的問題
那么寫會有什么問題么?答案是寫出中文不會有算命會造成亂碼的問題!寫不會有什么問題,寫不會有什么問題,重要的話說三遍。就像下面這樣:

public static void demo() throws FileNotFoundException, IOException {
        FileOutputStream fos = new FileOutputStream("zzz.txt");
        fos.write("我讀書少,你不要騙我".getBytes());
        fos.write("\r\n".getBytes());
        fos.close();
    }

隨便測試,不會出錯的。

  • 字節(jié)流直接操作的字節(jié),所以寫出中文必須將字符串轉(zhuǎn)換成字節(jié)數(shù)組
  • 寫出回車換行 write("\r\n".getBytes());
    只是讀寫中文的話(中間不進(jìn)行任何操作),雖然讀出來會亂碼,但是寫出來后又會恢復(fù)原樣,當(dāng)然,這時候?qū)懙臅r候就不需要用什么getBytes()方法了。

END

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、IO流 1.1、概述 之前學(xué)習(xí)的File類它只能操作文件或文件夾,并不能去操作文件中的數(shù)據(jù)。真正保存數(shù)據(jù)的是文...
    Villain丶Cc閱讀 2,792評論 0 5
  • 概述: 1、IO流:即Input Output的縮寫。 2、特點:1)IO流用來處理設(shè)備間的數(shù)據(jù)傳輸。2)Java...
    玉圣閱讀 1,325評論 0 3
  • [TOC] IO流 IO流概述及其分類 IO概念 IO流用來處理設(shè)備之間的數(shù)據(jù)傳輸,Java對數(shù)據(jù)的操作是通過流的...
    wh_閱讀 7,361評論 0 22
  • 開學(xué)了,我們來到學(xué),剛來到學(xué)校就迎接一場考試,我們以為有多難,原來是寒假生活上的題,而且還是單面,兩張,可以算是一...
    火鳳凰_2638閱讀 230評論 0 0
  • 下雨了,窩在床上看電影《裁縫》,很早很早就有人推薦我看,一直拖到現(xiàn)在。 我想說,深深的被里面的服飾折服。那個年代的...
    李涂涂閱讀 707評論 0 6

友情鏈接更多精彩內(nèi)容