Java---IO流知識總結

一、結構圖



二、分類
    IO 流:用於處理設備上的數據。設備:硬盤,內存,鍵盤錄入。
    
    IO流分類:
        1,根據處理的數據類型不同:字節流和字符流。
        2,根據流向不同:輸入流和輸出流。

    字符流的由來:
        因爲文件編碼的不同,而有了對字符進行高效操作的字符流對象。
        原理:其實就是基於字節流讀取字節時,去查了指定的碼錶。

    字節流和字符流的區別:
        1,字節流讀取的時候,讀到一個字節就返回一個字節。
             字符流使用了字節流讀到一個或多個字節(中文對應的字節數是兩個,在 UTF-8 碼錶中是 3 個字節)時。先去查指定的編碼表,將查到的字符返回。

        2,字節流可以處理所有類型數據,如圖片,mp3,avi。 而字符流只能處理字符數據。

結論:只要是處理純文本數據,就要優先考慮使用字符流。除此之外都用字節流。

IO 體系所具備的基本功能就有兩個:讀 和 寫。
    1,字節流:InputStream(讀),OutputStream(寫)。
    2,字符流:Reader(讀),Writer(寫)。

基本的讀寫操作方式:
        因爲數據通常都以文件形式存在,所以就要找到 IO 體系中可以用於操作文件的流對象。通過名稱可以更容易獲取該對象。
        因爲 IO 體系中的子類名後綴,絕大部分是父類名稱。而前綴,都是體現子類功能的名字。

字符流:
Reader
    |--InputStreamReader
        |--FileReader:專門用於處理文件的字符讀取流對象。

Writer
    |--OutputStreamWriter
       |--FileWriter:專門用於處理文件的字符寫入流對象。

Reader 中的常見的方法:
    1,int read():讀取一個字符。返回的是讀到的那個字符。如果讀到流的末尾,返回-1.
    2,int read(char[]):將讀到的字符存入指定的數組中,返回的是讀到的字符個數,也就是往數組裏 裝的元素的個數。如果讀到流的末尾,返回-1.
    3,close(): 讀取字符其實用的是 window 系統的功能,就希望使用完畢後,進行資源的釋 放。

Writer 中的常見的方法:
    1,write(ch): 將一個字符寫入到流中。
    2,write(char[]): 將一個字符數組寫入到流中。
    3,write(String): 將一個字符串寫入到流中。
    4,flush():刷新流,將流中的數據刷新到目的地中,流還存在。
    5,close():關閉資源:在關閉前會先調用 flush(),刷新流中的數據去目的地。然流 關閉。

FileWriter:
    該類沒有特有的方法。只有自己的構造函數。
    特點:
       1,用於處理文本文件。
       2,該類中有默認的編碼表,
        3,該類中有臨時緩衝。
    
構造函數:在寫入流對象初始化時,必須要有一個存儲數據的目的地。
    FileWriter(String filename):
    該構造函數做了什麼事情呢?
        1,調用系統資源。
        2,在指定位置,創建一個文件。
              注意:如果該文件已存在,將會被覆蓋。

    FileWriter(String filename,boolean append):該構造函數:
         當傳入的 boolean 類型值爲 true 時,會在指定文件末尾處進行數 據的續寫。

FileReader:
    1,用於讀取文本文件的流對象。
    2,用於關聯文本文件。
    構造函數:在讀取流對象初始化的時候,必須要指定一個被讀取的文件。
    
    如果該文件不存在會發生 FileNotFoundException.
    FileReader(String filename);

    對於讀取或者寫入流對象的構造函數,以及讀寫方法, 還有刷新關閉功能都會拋出 IOException 或其子類。所以都要進行處理。或者 throws 拋出,或者 try catch處理;

另一個小細節:
    當指定絕對路徑時,定義目錄分隔符有兩種方式:
        1,反斜線 但是一定要寫兩個。\\ new FileWriter("c:\\demo.txt");
        2,斜線 /
    寫一個即可。 new FileWriter("c:/demo.txt”);

字符流的緩衝區:
    緩衝區的出現提高了對流的操作效率。
    原理:其實就是將數組進行封裝。
    對應的對象:
        BufferedWriter:
            特有方法:newLine():寫一個換行符,跨平臺的換行符。
        BufferedReader:
            特有方法:readLine():一次讀一行,到行標記時,將行標記之前的字符數據作爲字符串返 回。當讀到末尾時,返回 null。不包含回車符。

    在使用緩衝區對象時,要明確,緩衝的存在是爲了增強流的功能而存在, 所以在建立緩衝區對象時,要先有流對象存在。
    其實緩衝內部就是在使用流對象的方法,只不過加入了數組對數據進行了臨時存儲。爲 了提高操作數據的效率。

代碼上的體現:
    寫入緩衝區對象。
    //建立緩衝區對象必須把流對象作爲參數傳遞給緩衝區的構造函數。
        BufferedWriter bufw = new BufferedWriter(new FileWriter("buf.txt"));
        bufw.write("abce");//將數據寫入到了緩衝區。
        bufw.flush();//對緩衝區的數據進行刷新。將數據刷到目的地中。
        bufw.close();//關閉緩衝區,其實關閉的是被包裝在內部的流對象。
    讀取緩衝區對象。
        BufferedReader bufr = new BufferedReader(new FileReader("buf.txt"));
        String line = null;
    //按照行的形式取出數據。取出的每一個行數據不包含回車符。
        while((line=bufr.readLine())!=null){
            System.out.println(line);
        }
        bufr.close();

練習:通過緩衝區的形式,對文本文件進行拷貝。
    public static void main(String[] args){
        BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
        BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
        String line = null;
        
        while((line=bufr.readLine())!=null){
            bufw.write(line);
            bufw.newLine();
            bufw.flush();
        }
        bufw.close();
        bufr.close();
    }

readLine():方法的原理
    其實緩衝區中的該方法,用的還是與緩衝區關聯的流對象的 read 方法。只不過,每一次讀到一個字符,先不進行具體操作,先進行臨時存儲。
    當讀取到回車標記時,將臨時容器中存儲的數據一次性返回。

    它的出現基於流並增強了流的功能這也是一種設計模式的體現:裝飾設計模式 對一組對象進行功能的增強。
    該模式和繼承有什麼區別呢?它比繼承有更好的靈活性。通常裝飾類和被裝飾類都同屬與一個父類或者接口。

字節流:
    字節流可以操作任何數據。
    抽象基類:InputStream,OutputStream。
    注意:字符流使用的數組是字符數組。char [] chs
                      字節流使用的數組是字節數組。byte [] bt

    FileOutputStream fos = new FileOutputStream("a.txt");
    fos.write("abcde");//直接將數據寫入到了目的地。
    fos.close();//只關閉資源。
    FileInputStream fis = new FileInputStream("a.txt");
    //fis.available();//獲取關聯的文件的字節數。

    //如果文件體積不是很大。可以這樣操作。但是這有一個弊端,就是文件過大,大小超出 jvm 的內容空間時,會內存溢出。 
      byte[] buf = new byte[fis.available()];//創建一個剛剛好的緩衝區。
      fis.read(buf);
      System.out.println(new String(buf));

轉換流:
    特點:
        1,是字節流和字符流之間的橋樑。
        2,該流對象中可以對讀取到的字節數據進行指定編碼表的編碼轉換。

    什麼時候使用呢?
        1,當字節和字符之間有轉換動作時。
        2,流操作的數據需要進行編碼表的指定時。

    具體的對象體現:
        1,InputStreamReader:字節到字符的橋樑。
        2,OutputStreamWriter:字符到字節的橋樑。

    這兩個流對象是字符流體系中的成員。那麼它們有轉換作用,而本身又是字符流。所以在構造的時候,需要傳入字節流對象進 來

    構造函數:
        InputStreamReader(InputStream):通過該構造函數初始化,使用的是本系統默認的編碼表 GBK。
        InputStreamReader(InputStream,String charSet):通過該構造函數初始化,可以指定編碼表。
        OutputStreamWriter(OutputStream):通過該構造函數初始化,使用的是本系統默認的編 碼錶 GBK。
        OutputStreamWriter(OutputStream,String charSet):通過該構造函數初始化,可以指定編碼 表。

    操作文件的字符流對象是轉換流的子類。
        Reader
            |--InputStreamReader
            |--FileReader

       Writer
            |--OutputStreamWriter
            |--FileWriter

    轉換流中的 read 方法。已經融入了編碼表在底層調用字節流的 read 方法時將獲取的一個或者多個字節數據進行臨時存儲,並去查指定的編碼表,如果編碼        表沒有指定,查的是默認碼錶。那麼轉流的 read 方法就可以返回一個字符比如中文。
    轉換流已經完成了編碼轉換的動作,對於直接操作的文本文件的 FileReaer 而言,就不 用在重新定義了,只要繼承該轉換流,獲取其方法,就可以直接操作文本文件中的字符數據了。

注意:
    在使用 FileReader 操作文本數據時,該對象使用的是默認的編碼表。如果要使用指定編碼表時,必須使用轉換流。

    FileReader fr = new FileReader("a.txt");//操作 a.txt 的中的數據使用的本系統默認的 GBK 。操作 a.txt 中的數據使用的也是本系統默認的 GBK。
    InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
    這兩句的代碼的意義相同。

    如果 a.txt 中的文件中的字符數據是通過 utf-8 的形式編碼。那麼在讀取時,就必須指定編碼表。
    那麼轉換流必須使用:InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"utf-8”);

IO 其他對象:

1,打印流,
        PrintStream:
            是一個字節打印流,System.out 對應的類型就是 PrintStream。它的構造函數可以接收三種數據類型的值。
            1,字符串路徑。
           2,File 對象。
           3,OutputStream。
        
        PrintWriter:
            是一個字符打印流。構造函數可以接收四種類型的值。
            1,字符串路徑。
            2,File 對象。
            對於 1,2 類型的數據,還可以指定編碼表。也就是字符集。
            3,OutputStream
            4,Writer
            對於 3,4 類型的數據,可以指定自動刷新。
            注意:該自動刷新值爲 true 時,只有三個方法可以用:println,printf,format.
            
        如果想要既有自動刷新,又可執行編碼。如何完成流對象的包裝?
           PrintWrter pw = new PrintWriter(new OutputSteamWriter(new FileOutputStream("a.txt"),"utf-8"),true);
        如果想要提高效率。還要使用打印方法。
           PrintWrter pw = new PrintWriter(new BufferdWriter(new OutputSteamWriter(new FileOutputStream("a.txt"),"utf-8")),true);
    
2,管道流。
       PipedInputStream 和 PipedOutputStream
    特點:
       讀取管道流和寫入管道流可以進行連接。
    連接方式:
        1,通過兩個流對象的構造函數。
        2,通過兩個對象的 connect 方法。

    通常兩個流在使用時,需要加入多線程技術,也就是讓讀寫同時運行。注意;對於 read 方法。該方法是阻塞式的,也就是沒有數據的情況,該方法會等待。

3,RandomAccessFile:
        該對象並不是流體系中的一員。該對象中封裝了字節流,同時還封裝了一個緩衝區(字節數組),通過內部的指針來操作 數組中的數據。

        特點:
        1,該對象只能操作文件,所以構造函數接收兩種類型的參數。
            a,字符串路徑。
            b,File 對象。
        2,該對象既可以對文件進行讀取,也可以寫入。
            在進行對象實例化時,必須要指定的該對象的操作模式,r rw 等。

    該對象中有可以直接操作基本數據類型的方法。
    該對象最有特點的方法:
        skipBytes():跳過指定的字節數。
        seek():指定指針的位置。
        getFilePointer():獲取指針的位置。
    通過這些方法,就可以完成對一個文件數據的隨機的訪問想讀哪裏就讀哪裏,想往哪裏寫就往哪裏寫。
    該對象功能,可以讀數據,可以寫入數據,如果寫入位置已有數據,會發生數據覆蓋。 也就是可以對數據進行修改。
    在使用該對象時,建議數據都是有規則的。或者是分段的。

    注意;該對象在實例化時,如果要操作的文件不存在,會自動建立。
              如果要操作的文件存在,則不會建立,如果存在的文件有數據,那麼在沒有指定指針位置的情況下,寫入數據,會將文件開頭的數據覆蓋。
               可以用於多線程的下載,也就是通過多線程往一個文件中同時存儲數據。

序列流。也稱爲合併流。
    SequenceInputStream:
    特點:可以將多個讀取流合併成一個流。這樣操作起來很方便。
    原理:其實就是將每一個讀取流對象存儲到一個集合中。最後一個流對象結尾作爲這個 流的結尾。

    兩個構造函數:
        1,SequenceInputStream(InputStream in1,InputStream in2)  
            可以將兩個讀取流合併成一個流。
        2,SequenceInputStream(Enumeration<? extends InputStream> en) 
            可以將枚舉中的多個流合併成一個流。 
            作用:可以用於多個數據的合併。
  
 如果要一個對文件數據切割。 一個讀取對應多了輸出。
    FileInputStream fis = new FileInputStream("1.mp3");
    FileOutputStream fos = null;
    byte[] buf = new byte[1024*1024]; //是一個 1MB 的緩衝區。

    int len = 0;
    int count = 1;

    while((len=fis.read(buf))!=-1) {
        fos = new FileOutputStream((count++)+".part);
        fos.write(buf,0,len);
        fos.close();
    }
    fis.close();

    這樣就是將 1.mp3 文件切割成多個碎片文件。想要合併使用 SequenceInputStream 即可。
    對於切割後,合併是需要的一些源文件的信息。可以通過配置文件進行存儲。該配置可以通過鍵=值的形式存在。然後通過 Properties 對象進行數據的加載和獲取。

File 類:
    該類的出現是對文件系統的中的文件以及文件夾進行對象的封裝
    可以通過對象的思想來操作文件以及文件夾。

    1,構造函數:
            File(String filename):將一個字符串路徑(相對或者絕對)封裝成 File 對象,該路徑是可存在的,也可以是不存在(需要判斷)。
            File(String parent,String child);
            File(File parent,String child);

    2,特別的字段:separator:跨平臺的目錄分隔符。
          例子:File file = new File("c:"+File.separator+"abc"+File.separator+"a.txt");

    3,常見方法:
        1,創建:
            boolean createNewFile () throws IOException: 創建文件,如果被創建的文件已經存在,則不創建。
            boolean mkdir( ): 創建文件夾。
            boolean mkdirs( ): 創建多級文件夾。

        2,刪除:
            boolean delete( ):可用於刪除文件或者文件夾。
                    注意:對於文件夾只能刪除不帶內容的空文件夾對於帶有內容的文件夾,不可以直接刪除,必須要從裏往外刪除。
            void deleteOnExit( ): 刪除動作交給系統完成。無論是否反生異常,系統在退出時執行刪除動作。

        3,判斷:
            boolean canExecute():
            boolean canWrite( ):
            boolean canRead( );
            boolean exists( ):判斷文件或者文件夾是否存在。
            boolean isFile( ): 判斷 File 對象中封裝的是否是文件。
            boolean isDirectory( ):判斷 File 對象中封裝的是否是文件夾。
            boolean isHidden( ):判斷文件或者文件夾是否隱藏。在獲取硬盤文件或者文件夾 時,
            對於系統目錄中的文件,java 是無法訪問的,所以在遍歷,可以避免遍歷 隱藏文件。

        4,獲取:
            getName( ):獲取文件或者文件夾的名稱。
            getPath( ):File 對象中封裝的路徑是什麼,獲取的就是什麼。
            getAbsolutePath( ):無論 File 對象中封裝的路徑是什麼,獲取的都是絕對路徑。
            getParent( ): 獲取 File 對象封裝文件或者文件夾的父目錄。
           注意:如果封裝的是相對路徑,那麼返回的是 null.

            long length( ):獲取文件大小。
            long lastModified( ):獲取文件或者文件最後一次修改的時間。
            static File [] listRoots():獲取的是被系統中有效的盤符。
            String[] list( ):獲取指定目錄下當前的文件以及文件夾名稱。
            String[] list(Filenamefilter): 可以根據指定的過濾器,過濾後的文件及文件夾名 稱。
            File[] listFiles( ):獲取指定目錄下的文件以及文件夾對象。

        5,重命名:
            renameTo(File):
            File f1 = new File("c:\\a.txt");
            File f2 = new File("c:\\b.txt");
            f1.renameTo(f2);//將 c 盤下的 a.txt 文件改名爲 b.txt 文件。

操作基本數據類型的流對象
    DataInputStream
        DataInputStream(InputStream);
    操作基本數據類型的方法:
        int readInt():一次讀取四個字節,並將其轉成 int 值。
        boolean readBoolean():一次讀取一個字節。
        short readShort();
        long readLong();
               剩下的數據類型一樣。
        String readUTF():按照 utf-8 修改版讀取字符。注意,它只能讀 writeUTF()寫入的字符 數據。

    DataOutputStream
        DataOutputStream(OutputStream):
    操作基本數據類型的方法:
        writeInt(int):一次寫入四個字節。
            注意和 write(int)不同。write(int)只將該整數的最低一個 8 位寫入。剩餘三個 8 位丟棄。
        writeBoolean(boolean);
        writeShort(short);
        writeLong(long);
            剩下是數據類型也也一樣。
        writeUTF(String):按照 utf-8 修改版將字符數據進行存儲。只能通過 readUTF 讀取。
        通常只要操作基本數據類型的數據。就需要通過 DataStram 進行包裝。

        通常成對使用。

操作數組的流對象
1,操作字節數組
        ByteArrayInputStream

        ByteArrayOutputStream
            toByteArray();
            toString();
            writeTo(OutputStream);

2,操作字符數組。
        CharArrayReader
        CharArrayWriter

        對於這些流,源是內存。目的也是內存而且這些流並未調用系統資源。使用的就是內存中的數組。所以這些在使用的時候不需要 close。
        操作數組的讀取流在構造時,必須要明確一個數據源。所以要傳入相對應的數組。對於操作數組的寫入流,在構造函數可以使用空參數。因爲它內置了一個可變長度數組 作爲緩衝區。

        這幾個流的出現其實就是通過流的讀寫思想在操作數組。類似的對象同理:StringReader,StringWriter

編碼轉換
        在 io 中涉及到編碼轉換的流是轉換流和打印流。但是打印流只有輸出。在轉換流中是可以指定編碼表的。默認情況下,都是本機默認的碼錶。GBK. 這個編碼表怎麼來的? System.getProperty("file.encoding");

常見碼錶:
    ASCII:美國標準信息交換碼。使用的是 1 個字節的 7 位來表示該表中的字符。
    ISO8859-1:拉丁碼錶。使用 1 個字節來表示。
    GB2312:簡體中文碼錶。
    GBK:簡體中文碼錶,比 GB2312 融入更多的中文文件和符號。
    unicode:國際標準碼錶。都用兩個字節表示一個字符。
    UTF-8:對 unicode 進行優化,每一個字節都加入了標識頭。

編碼轉換:
    字符串 —> 字節數組 :編碼。通過 getBytes(charset);
    字節數組-->字符串 : 解碼。通過 String 類的構造函數完成。String(byte[],charset);
    如果編錯了,沒救!如果編對了,解錯了,有可能還有救!

    String s = "你好";
    //編碼。
        byte[] b = s.getBytes("GBK");
    //解碼。
        String s1 = new String(b,"iso8859-1");
        System.out.println(s1);

    //想要還原。
    /* 對 s1 先進行一次解碼碼錶的編碼。獲取原字節數據。
        然後在對原字節數據進行指定編碼表的解碼。
    */
        byte[] b1 = s1.getBytes("iso8859-1");
        String s2 = new String(b1,"gbk");
        System.out.println(s2);//你好。

        這種情況在 tomcat 服務器會出現。因爲 tomcat 服務器默認是 iso8859-1 的編碼表。所以客戶端通過瀏覽器向服務端通過 get 提交方式提交中文數據時,服務端獲取到會使用 ISO8859-1 進行中文數據解碼。會出現亂碼。這時就必須要對獲取的數據進行 iso8859-1 編碼。然後在按照頁面指定的編碼表進行解碼即可。
        而對於 post 提交,這種方法也通用。但是 post 有更好的解決方式。request.setCharacterEncoding("utf-8");即可。所以建立客戶端提交使用 post 提交方式。

相關知識:

1.如果啓用了自動刷新,則只有在調用 println、printf 或 format 的能夠實現數據寫入到流所對應的文件

2.序列化流: 把對象寫入到流中
        ObjectOutputStream

         構造方法:
             ObjectOutputStream()
             ObjectOutputStream(OutputStream out)
         
         方法:
             public final void writeObject(Object obj) throws IOException
             將指定的對象寫入 ObjectOutputStream

 反序列化流:從流中讀取對象
         ObjectInputStream
             構造方法:
                ObjectInputStream()
                ObjectInputStream(InputStream in)

             方法:
             public final Object readObject() throws IOException, ClassNotFoundException
             從 ObjectInputStream 讀取對象
注意: 使用序列化與反序列化的類通過實現 java.io.Serializable 接口以啓用其序列化功能。
            未實現此接口的類將無法使其任何狀態序列化或反序列化,序列化接口沒有方法或字段,僅用於標識可序列化的語義。
            使用transient關鍵字聲明不需要序列化的成員變量。

Properties和IO流的結合使用
    public void load(InputStream stream)
    public void load(Reader reader) 把流所對應的文件中的數據,讀取到集合中
    public void store(OutputStream stream,String comments)
    public void store(Writer writer,String comments) 把集合中的數據,存儲到流所對應的文件中

close()和flush()的區別:
    flush():將緩衝區的數據刷到目的地中後,流可以使用。
    close():將緩衝區的數據刷到目的地中後,流就關閉了,該方法主要用於結束調用的底層資源。這個動作一定做。
        //記住,只要一讀取鍵盤錄入,就用這句話。
           BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        //輸出到控制檯
            BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

流的操作規律:
1,明確數據源和目的地。(明確輸入流還是輸出流。
      數據源:就是需要讀取,可以使用兩個體系:InputStream、Reader;
      目的地:就是需要寫入,可以使用兩個體系:OutputStream、Writer;

2,操作的數據是否是純文本數據?(明確字符流還是字節流
      如果是:數據源:Reader
                    目的地:Writer
      如果不是:數據源:InputStream
                        目的地:OutputStream

3,雖然確定了一個體系,但是該體系中有太多的對象,到底用哪個呢?
        明確操作的數據設備。
            數據源對應的設備:硬盤(File),內存(數組),鍵盤(System.in)
            數據匯對應的設備:硬盤(File),內存(數組),控制檯(System.out)。

4,需要在基本操作上附加其他功能嗎?比如緩衝。
        如果需要就進行裝飾。

   數據匯:一個文件,硬盤。
        既然是數據匯,那麼一定是輸出流,可以用的 OutputStream,Writer。往文件中存儲的都是文本數據,那麼可以使用字符流較爲方便:Writer.
        因爲操作的是一個文件。所以使用 Writer 中的 FileWriter。
        是否要提高效率呢?是,那就使用 BufferedWriter.  
            BufferedWriter bufr = new BufferedWriter(new FileWriter("a.txt"));

需求:
1,將鍵盤錄入的數據存儲到一個文件中。
        數據源:System.in。
        既然是源,使用的就是輸入流,可用的體系有 InputStream,Reader。
        因爲鍵盤錄入進來的一定是純文本數據,所以可以使用專門操作字符數據的 Reader。發現System.in 對應的流是字節讀取流。所以要將其進行轉換,將字節轉成 字符即可。所以要使用 Reader 體系中:InputStreamReader。
        接下來,是否需要提高效率呢?如果需要,那麼就加入字符流的緩衝區: BufferedReader
        BufferedReader bur = new BufferedReader(new InputStreamReader(System.in));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章