Java基礎(chǔ)進(jìn)階 IO流之字節(jié)流

1、IO流

1.1、概述

之前學(xué)習(xí)的File類它只能操作文件或文件夾,并不能去操作文件中的數(shù)據(jù)。真正保存數(shù)據(jù)的是文件,數(shù)據(jù)是在文件中。而File類它只是去對文件本身做操作,不能對文件中的數(shù)據(jù)進(jìn)行操作。
如果要操作文件中的數(shù)據(jù),這時(shí)必須使用Java中提供的IO流技術(shù)完成。
IO可以實(shí)現(xiàn)數(shù)據(jù)的傳輸,把數(shù)據(jù)比作是像水一樣在流動(dòng)。
IO:input/output
IO流:它的功能就是專門操作文件中的數(shù)據(jù)。

2.2、IO流分類

1)按流向分:
輸入流:讀取數(shù)據(jù),把持久設(shè)備的數(shù)據(jù)讀取到內(nèi)存中。
輸出流:寫出數(shù)據(jù),把內(nèi)存的數(shù)據(jù)寫出到持久設(shè)備。
2)按數(shù)據(jù)類型分:
計(jì)算機(jī)中一切數(shù)據(jù)都是:字節(jié)數(shù)據(jù)。
字符數(shù)據(jù):底層還是字節(jié)數(shù)據(jù),但是可以根據(jù)某些規(guī)則,把字節(jié)變成人們認(rèn)識(shí)的文字、符號(hào)等等。
字節(jié)流:數(shù)據(jù)在持久設(shè)備上都是以二進(jìn)制形式保存的。二進(jìn)制就是字節(jié)數(shù)據(jù)。Java就給出了字節(jié)流可以直接操作字節(jié)數(shù)據(jù)。

字節(jié)輸入流:InputStream
兒子:XxxInputStream
字節(jié)輸出流:OutputStream
兒子:XxxOutputStream

字符流:讀取字符數(shù)據(jù)。數(shù)據(jù)在設(shè)備上是以二進(jìn)制形式表示,但是有些二進(jìn)制合并在一起可以表示一些字符數(shù)據(jù)。

字符輸入流:Reader
兒子:XxxReader

字符輸出流:Writer
兒子:XxxWriter

IO流的分類如下圖所示:


IO流的分類

說明:
1)字節(jié)流可以對任意類型的文件按照字節(jié)進(jìn)行讀和寫的操作;
例如:圖片、視頻、文本文件、word文檔、mp3等。
2)字符流只能對文本類型的文件進(jìn)行操作;
問題1:文本類型的文件是什么文件?
只要可以使用記事本打開并看得懂的文件就是文本文件。
例如:.java文件、.txt等文件。
而字符流只能操作文本類型的文件,也就是說如果一個(gè)文件可以使用記事本打開并能夠看懂,那么這個(gè)文件就可以使用字符流來操作,否則其他的文件都得使用字節(jié)流進(jìn)行操作。

注意:字節(jié)流也可以操作文本文件。

2 字節(jié)流

2.1、字節(jié)流介紹

字節(jié)流:它是以字節(jié)為單位,讀寫數(shù)據(jù)的。

讀寫:
讀是從持久設(shè)備上給程序讀取數(shù)據(jù)。(硬盤-------》內(nèi)存)
寫是把程序中的數(shù)據(jù)寫到持久設(shè)備上。(內(nèi)存-------》硬盤)
不管是字節(jié)流還是字符流,他們都有讀和寫的操作。
字節(jié)流:它分成字節(jié)輸入流和字節(jié)輸出流。
字節(jié)輸入流:從持久設(shè)備上把數(shù)據(jù)讀取到程序中。
字節(jié)輸出流:把程序中的數(shù)據(jù)寫到持久設(shè)備上。
所有的數(shù)據(jù)都可以使用字節(jié)流操作。
常見的數(shù)據(jù)保存形式:記事本、word文檔、圖片、音頻、視頻、壓縮文件等

2.2、字節(jié)輸出流(掌握)

之前我們在學(xué)習(xí)其他類的時(shí)候,例如異常、集合都有頂層父類或者頂層接口。那么在字節(jié)流中也有頂層父類,規(guī)定了字節(jié)輸入流或字節(jié)輸出流的基本操作行為。

字節(jié)流:
字節(jié)輸出:Output 字節(jié)輸出流:OutputStream
字節(jié)輸入:Input 字節(jié)輸入流:InputStream


1.png

OutputStream:它是字節(jié)輸出流的頂層父類。它可以把字節(jié)數(shù)據(jù)寫給JVM,JVM在交給操作系統(tǒng),操作系統(tǒng)把數(shù)據(jù)寫到持久設(shè)備上。

OutputStream類中的函數(shù)如下所示:


2.png

注意: 學(xué)習(xí)IO流,我們是在使用Java代碼操作Java以外的其他設(shè)備。不管操作中是否有問題,最后都要斷開Java程序和這些設(shè)備之間的連接。

   close方法是關(guān)閉Java和其他設(shè)備之間的連接。
   write方法是把數(shù)據(jù)寫到Java關(guān)聯(lián)的設(shè)備中
   write(byte[] b ) 把這個(gè)b字節(jié)數(shù)組中的所有數(shù)據(jù)寫到關(guān)聯(lián)的設(shè)備中(設(shè)備包括文件、網(wǎng)絡(luò)或者其他任何地方)。
   write(byte[] b , int off , int len ) 把b字節(jié)中的數(shù)據(jù)從下標(biāo)off位置開始往出寫,共計(jì)寫len個(gè)
   write(int b ) 把這個(gè)b數(shù)據(jù)寫到關(guān)聯(lián)的設(shè)備中。 

OutputStream:它是抽象類,不能創(chuàng)建對象,這里我們需要使用OutputStream類的子類FileOutputStream的對象把數(shù)據(jù)寫到文件中。


3.png

構(gòu)造方法如下所示:


4.png

FileOutputStream(Filefile)
創(chuàng)建一個(gè)向指定 File 對象表示的文件中寫入數(shù)據(jù)的文件輸出流。

FileOutputStream(String name)
創(chuàng)建一個(gè)向具有指定名稱的文件中寫入數(shù)據(jù)的輸出文件流。

需求1:將一個(gè)字節(jié)byte數(shù)組{97,98,65,66}中的數(shù)據(jù)寫到D:\test1\1.txt 的文件中。

分析:
構(gòu)造函數(shù):
FileOutputStream(File file)
創(chuàng)建一個(gè)向指定 File 對象表示的文件中寫入數(shù)據(jù)的文件輸出流。


5.png

FileOutputStream(String name)
創(chuàng)建一個(gè)向具有指定名稱的文件中寫入數(shù)據(jù)的輸出文件流。

步驟:
1)創(chuàng)建一個(gè)測試類FileOutputStreamDemo;
2)在這個(gè)類中定義一個(gè)method_1函數(shù),在這個(gè)函數(shù)中創(chuàng)建FileOutputStream類的對象out,分別使用上述兩種構(gòu)造函數(shù)創(chuàng)建,并在構(gòu)造函數(shù)的參數(shù)中指定目錄路徑D:\test1\1.txt;
3)定義一個(gè)字節(jié)byte數(shù)組b,并存入值;
4)使用字節(jié)輸出流對象out調(diào)用write()函數(shù),將字節(jié)數(shù)組中的數(shù)據(jù)寫到指定的文件中;
5)使用對象out調(diào)用close()函數(shù)關(guān)閉流資源;

//需求1:將一個(gè)字節(jié)byte數(shù)組中的數(shù)據(jù)寫到D:\\test1\\1.txt 的文件中。
    public static void method_1() throws IOException {
        // 創(chuàng)建字節(jié)輸出流對象 FileOutputStream(String name) 
        /*
         *  public FileOutputStream(String name) throws FileNotFoundException 
         *  {
                this(name != null ? new File(name) : null, false);
            }
         */
        //經(jīng)常用 底層幫我們將字符串封裝成了File類的對象
        OutputStream out = new FileOutputStream("D:\\test1\\1.txt");
        //FileOutputStream(File file) 
        //創(chuàng)建File類的對象 
    /*  File file=new File("D:\\test1\\1.txt");
        //創(chuàng)建輸出流對象  不經(jīng)常使用
        FileOutputStream out = new FileOutputStream(file);*/
        //創(chuàng)建一個(gè)字節(jié)數(shù)組
        byte[] b={97,98,65,66};
        //將字節(jié)數(shù)組中的數(shù)據(jù)寫到指定的文件中
        out.write(b);
        //關(guān)閉輸出流
        out.close();
}

注意:
問題1:構(gòu)造函數(shù)執(zhí)行的時(shí)候,做了什么?
A:判斷目標(biāo)文件所在的目錄路徑在硬盤上是否存在,如果路徑不存在,那么就會(huì)報(bào)系統(tǒng)找不到指定的路徑的異常FileNotFoundException。


6.png

B:如果目標(biāo)文件所在的路徑存在了,也就是說D:\test1,那么接下來就會(huì)判斷目標(biāo)文件1.txt在指定的目錄中D:\test1中是否存在:
如果不存在,替我們創(chuàng)建1.txt文件出來,并將數(shù)據(jù)寫到1.txt文件中;
如果這個(gè)文件1.txt已經(jīng)存在,這時(shí)用新的數(shù)據(jù)覆蓋掉原來文件中的數(shù)據(jù);
C:調(diào)用系統(tǒng)資源,關(guān)聯(lián)目標(biāo)文件;

舉例:其實(shí)我們平常使用記事本或者其他軟件打開.txt文件的時(shí)候都是在調(diào)用系統(tǒng)資源,進(jìn)行關(guān)聯(lián)目標(biāo)文件。

問題2:為什么要close?
A:流關(guān)閉后,就會(huì)釋放系統(tǒng)資源;
B:流關(guān)閉,就變成了垃圾數(shù)據(jù),這樣可以被垃圾回收器回收,免得沒有用的資源浪費(fèi)空間;

問題3:關(guān)于FileOutputStream輸出流的另一個(gè)構(gòu)造函數(shù)


7.png

FileOutputStream(File file)如何使用?
其實(shí)對于FileOutputStream(String name) 這個(gè)構(gòu)造函數(shù)在底層幫我們將字符串封裝成了File類的對象,作為程序員我們不用再創(chuàng)建File類的對象了,所以開發(fā)中建議使用FileOutputStream(Stringname)這個(gè)構(gòu)造函數(shù)。

需求2:使用字節(jié)輸出流把字符串?dāng)?shù)據(jù)”hello,編程”寫到硬盤D:\test1\2.txt上;
1)在上述測試類中在創(chuàng)建一個(gè)method_2()函數(shù);
2)在這個(gè)函數(shù)中創(chuàng)建FileOutputStream類的對象out,并在構(gòu)造函數(shù)的參數(shù)中指定目錄路徑D:\test1\2.txt;
3)定義一個(gè)字符串s=”hello,狗哥”;
4)使用字符串對象s調(diào)用String類中的函數(shù)將字符串轉(zhuǎn)成字節(jié)數(shù)組;
5)使用字節(jié)輸出流對象out調(diào)用write()函數(shù),將字節(jié)數(shù)組中的數(shù)據(jù)寫到指定的文件中;
6)使用對象out調(diào)用close()函數(shù)關(guān)閉流資源;

//需求2:使用字節(jié)輸出流把字符串?dāng)?shù)據(jù)”hello,狗哥”寫到硬盤上;
    public static void method_2() throws IOException {
        //創(chuàng)建輸出流對象
        OutputStream out = new FileOutputStream("D:\\test1\\2.txt");
        //定義一個(gè)字符串
        String s="hello,狗哥";
        /*
         * 使用輸出流對象調(diào)用write函數(shù)將數(shù)據(jù)寫到硬盤上指定的文件中
         * 將字符串s轉(zhuǎn)換為字節(jié)數(shù)組
         */
//      out.write("hello 編程語言".getBytes());
        out.write(s.getBytes());
        //關(guān)閉資源
        out.close();
    }

需求3:演示write(int b) 了解使用謹(jǐn)慎
1)在上述測試類中在創(chuàng)建一個(gè)method_3()函數(shù);
2)在這個(gè)函數(shù)中創(chuàng)建FileOutputStream類的對象out,并在構(gòu)造函數(shù)的參數(shù)中指定目錄路徑D:\test1\3.txt;
3)使用字節(jié)輸出流對象out調(diào)用write()函數(shù),將整數(shù)97,353寫到指定的文件中;
4)使用對象out調(diào)用close()函數(shù)關(guān)閉流資源;

//演示write(int b)函數(shù)
    public static void method_3() throws IOException {
        // 創(chuàng)建輸出流對象
        OutputStream out=new FileOutputStream("D:\\test1\\3.txt");
//      OutputStream out1=new FileOutputStream("D:\\test1\\3.txt");
        //使用輸出流對象調(diào)用write函數(shù)寫出整數(shù)
        /*
         * 字節(jié)輸出流中的write方法每調(diào)用一次,只能寫出一個(gè)字節(jié)數(shù)據(jù)。
         * 如果指定的數(shù)據(jù)較大,這個(gè)時(shí)候它只會(huì)把這個(gè)數(shù)據(jù)中最低位上的1個(gè)字節(jié)數(shù)據(jù)寫到文件中
         * 97  :00000000 00000000 00000000 01100001 
         * 353 :00000000 00000000 00000001 01100001
         */
//      out.write(353);//a
//      out.write(97);//a
        /*
         * public void write(byte[] b,int off,int len)
         * b表示字節(jié)數(shù)組
         * off表示從下標(biāo)為off開始
         * len表示寫到文件中的字節(jié)個(gè)數(shù)
         */
        byte[] b={97,98,65,66};
        out.write(b, 0, 2);//寫出結(jié)果是:ab
        //關(guān)閉資源
        out.close();
//      out1.write(b);
//      out.close();
//      out.write(97);//a
//      System.out.println(Integer.toBinaryString(353));
}

說明:
1)字節(jié)輸出流中的write方法每調(diào)用一次,只能寫出一個(gè)字節(jié)數(shù)據(jù)。
如果指定的數(shù)據(jù)較大,這個(gè)時(shí)候它只會(huì)把這個(gè)數(shù)據(jù)中最低位上的1個(gè)字節(jié)數(shù)據(jù)寫到文件中

例如:353輸出的結(jié)果是97.
           0000 0000 0000 0000 0000 0000 0110 0001  97
           0000 0000 0000 0000 0000 0001 0110 0001  353

2)public void write(byte[] b,intoff,int len)
b表示字節(jié)數(shù)組
off表示從下標(biāo)為off開始
len表示寫到文件中的字節(jié)個(gè)數(shù)
3)如果使用完一個(gè)輸出流,關(guān)閉之后,那么不能繼續(xù)使用這個(gè)輸出流向該文件中繼續(xù)寫入數(shù)據(jù),否則會(huì)報(bào)異常。只能重新再創(chuàng)建一個(gè)新的輸出流,繼續(xù)向該文件中寫數(shù)據(jù),這樣后寫入的數(shù)據(jù)會(huì)覆蓋之前書寫的數(shù)據(jù);

2.3、追加數(shù)據(jù)和換行

數(shù)據(jù)追加問題:
通過以上演示我們發(fā)現(xiàn)一個(gè)問題,就是流一旦關(guān)閉,再次寫數(shù)據(jù),會(huì)覆蓋文件中原來的數(shù)據(jù)。流不關(guān)閉,一次性寫多次,也會(huì)追加。

如何解決?
使用FileOutputStream類中的其余構(gòu)造函數(shù):
FileOutputStream(File file, booleanappend) 創(chuàng)建一個(gè)向指定 File 對象表示的文件中寫入數(shù)據(jù)的文件輸出流。
FileOutputStream(String name, boolean append) 創(chuàng)建一個(gè)向具有指定name 的文件中寫入數(shù)據(jù)的文件輸出流。

說明:
A:上述構(gòu)造函數(shù)中如果第二個(gè)參數(shù)append為true,那么就在已經(jīng)存在的文件末位追加數(shù)據(jù),如果為false,那么就不會(huì)文件中已經(jīng)存在的數(shù)據(jù)末尾追加數(shù)據(jù),而是將原來的數(shù)據(jù)給覆蓋;
B:如果指定的文件在硬盤上不存在,也會(huì)創(chuàng)建這個(gè)文件;
需求:使用字節(jié)輸出流把字符串?dāng)?shù)據(jù)”你好嗎”寫到硬盤上,要求不能覆蓋文件中原有的數(shù)據(jù);

分析和步驟:
1)使用new關(guān)鍵字調(diào)用FileOutputStream類的構(gòu)造函數(shù)創(chuàng)建輸出流對象fos;
2)使用對象fos調(diào)用write函數(shù)向指定的文件添加數(shù)據(jù);
3)關(guān)閉資源;

//演示向文件末尾追加數(shù)據(jù) 
    //需求:使用字節(jié)輸出流把字符串?dāng)?shù)據(jù)”你好嗎”寫到硬盤上,要求不能覆蓋文件中原有的數(shù)據(jù);
    public static void method_1() throws IOException {
        //創(chuàng)建輸出流對象 
        FileOutputStream fos = new FileOutputStream("D:\\test1\\4.txt", true);
        /*
         * 對于以上構(gòu)造函數(shù)進(jìn)行說明:
         * 如果第二個(gè)參數(shù)為true,那么就會(huì)在已經(jīng)存在的文件中的末尾處追加數(shù)據(jù),如果這個(gè)文件4.txt不存在
         * 那么就會(huì)創(chuàng)建這個(gè)文件
         * 如果第二個(gè)參數(shù)為false,那么向文件添加數(shù)據(jù)的時(shí)候就會(huì)覆蓋原來的數(shù)據(jù)
         */
        //向文件中添加數(shù)據(jù)
        fos.write("你好嗎".getBytes());
        fos.write("我叫狗哥".getBytes());
        //關(guān)閉資源
        fos.close();
    }

數(shù)據(jù)換行問題:
我們?nèi)绻霌Q行,可以在數(shù)據(jù)的末尾加:\r\n
但是:\r\n是windows系統(tǒng)識(shí)別的換行符。不同的操作系統(tǒng),換行符可能會(huì)不相同的。我們的代碼擴(kuò)展性就變差了。

解決方案:
如果我能根據(jù)系統(tǒng)來獲取對應(yīng)的換行符,就可以跨平臺(tái)。如何從系統(tǒng)獲取換行符呢?

System類中的方法:
public staticProperties getProperties() 獲取系統(tǒng)中所有的屬性這個(gè)函數(shù)的返回值是 Properties 類,這個(gè)類實(shí)現(xiàn)了Map接口,所以getProperties()這個(gè)函數(shù)獲得系統(tǒng)中所有的屬性并以鍵值對形式存在。在眾多系統(tǒng)屬性中我們想要的是行分隔符,就是類似\r\n,那么行分隔符的鍵是line.separator,也就是說通過這個(gè)鍵就可以獲得系統(tǒng)對應(yīng)的行分隔符。

這里還得需要借助一個(gè)函數(shù)通過以上的鍵line.separator獲得行分隔符的值,這個(gè)函數(shù)就是getProperty()
static String getProperty(String key) 獲取指定鍵指示的系統(tǒng)屬性。
代碼如下所示:

/*
     * 換行演示
     * 我們?nèi)绻霌Q行可以在添加數(shù)據(jù)的末尾書寫\r\n 就可以實(shí)現(xiàn)換行
     * 但是\r\n屬于Windows系統(tǒng)中特有的方法,不能在其他系統(tǒng)中使用,也就是說不能
     * 跨平臺(tái),這樣代碼的擴(kuò)展性就變差了
     * 解決辦法:
     *  如果我們能夠根據(jù)不同的系統(tǒng)獲取系統(tǒng)對應(yīng)的行分隔符,那么這樣代碼就可以跨平臺(tái)了,
     * 那么如何獲得系統(tǒng)的的屬性,行分隔符呢?
     * 通過System類,調(diào)用這個(gè)類中的函數(shù)getProperties()
     */
    public static void method_2() throws IOException {
        //創(chuàng)建輸出流對象
        FileOutputStream fos = new FileOutputStream("D:\\test1\\5.txt", true);
        //向文件中寫入數(shù)據(jù)
//      fos.write("hello 上海傳智\r\n".getBytes());
        //表示獲得系統(tǒng)中所有的屬性
        /*Properties properties = System.getProperties();
        System.out.println(properties);*/
        //獲得系統(tǒng)中的行分隔符
        String separator = System.getProperty("line.separator");
        //向文件中寫出數(shù)據(jù)
        fos.write(("狗哥真帥哈哈"+separator).getBytes());
        //關(guān)閉資源
        fos.close();
    }

2.4、字節(jié)輸入流(掌握)

之前學(xué)習(xí)的是輸出流對象,是用來從內(nèi)存中向文件(硬盤)中寫入數(shù)據(jù)。如果想要從文件(硬盤)中向內(nèi)存中讀取數(shù)據(jù),需要使用輸入流對象:InputStream。

2.4.1、InputSteam介紹

字節(jié)輸入流:InputStream:


8.png
java.lang.Object
   |----java.io.InputStream:屬于抽象類。是IO中所有的字節(jié)輸入流的父類。
                    該類中定義了所有字節(jié)輸入流的共性功能 

InputStream類中的共性功能:

9.png

說明:close(): 關(guān)閉字節(jié)輸入流對象。

10.png

說明:
1)read():調(diào)用一次read,就可以從關(guān)聯(lián)的文件中讀取一個(gè)字節(jié)數(shù)據(jù),并返回這個(gè)字節(jié)數(shù)據(jù)。
2)read():方法可以從關(guān)聯(lián)的文件中讀取數(shù)據(jù)。所有read方法如果讀取到文件的末尾,都會(huì)返回-1。遇到-1就代表文件中的數(shù)據(jù)已經(jīng)被讀取完畢。
3)read(byte[] b) :調(diào)用一次,讀取多個(gè)字節(jié)數(shù)據(jù),把讀到的字節(jié)數(shù)據(jù)保存在傳遞的b字節(jié)數(shù)組中。返回字節(jié)數(shù)組中讀取的字節(jié)個(gè)數(shù)。注意啦:這個(gè)返回值不是數(shù)組長度。

由于InputStream類是抽象類,不能創(chuàng)建這個(gè)類的對象,但是如果想使用這個(gè)類中的函數(shù),那必須得創(chuàng)建這個(gè)類的對象,如果想要?jiǎng)?chuàng)建對象,那么只能創(chuàng)建InputStream類的子類。

由于我們這里是操作文件的,所以我們需要?jiǎng)?chuàng)建FileInputStream類的對象。

2.4.2、FileInputStream介紹


11.png

2.4.3、每次讀單個(gè)字節(jié)
演示:字節(jié)輸入流讀取數(shù)據(jù),一次讀一個(gè)字節(jié)。
構(gòu)造函數(shù):
FileInputStream(File file)
FileInputStream(String name)
讀取功能:int read()
先使用輸入流對象,從文件中讀取數(shù)據(jù),每調(diào)用一次read()方法,可以從硬盤文件中讀取一個(gè)字節(jié)數(shù)據(jù),把這個(gè)字節(jié)數(shù)據(jù)保存在一個(gè)int類型的變量中。然后判斷讀取到的這個(gè)數(shù)據(jù)也就是這個(gè)int類型的變量是否是-1,如果不是-1,說明當(dāng)前沒有讀取到文件的末尾。如果是-1,說明當(dāng)前已經(jīng)讀取到文件的末尾。

int類型的變量中就保存著當(dāng)前讀取到的那個(gè)字節(jié)數(shù)據(jù),后續(xù)步驟可以對這個(gè)數(shù)據(jù)進(jìn)行相關(guān)處理。

輸入流的使用步驟:
A:創(chuàng)建輸入流,關(guān)聯(lián)源文件;
B:讀取數(shù)據(jù);
C:釋放資源;

注意:
1)字節(jié)輸入流,構(gòu)造函數(shù)執(zhí)行時(shí),如果源文件不存在,那么拋出異常!!;
2)由于輸入流讀取的是文件中的字節(jié)數(shù)據(jù),所以要求輸入流指定的一定是文件,不能是文件夾,否則會(huì)報(bào)異常;

分析和步驟:
1)使用new關(guān)鍵字調(diào)用FileInputStream類的構(gòu)造函數(shù)創(chuàng)建指定路徑D:\test1\1.txt的輸入流對象in;
2)使用輸入流對象in調(diào)用read()函數(shù)開始讀取文件,返回一個(gè)int類型的整數(shù),并輸出最后返回值;
3)多次調(diào)用read()函數(shù)引起代碼重復(fù),我們可以考慮使用循環(huán)來實(shí)現(xiàn);
4)循環(huán)條件是返回值是-1;

/*
 * 演示:字節(jié)輸入流讀取數(shù)據(jù),一次讀一個(gè)字節(jié)。
 * 注意:由于輸入流讀取的是文件中的字節(jié)數(shù)據(jù),所以要求輸入流指定的一定是文件,否則會(huì)報(bào)異常
 * FileNotFoundException
 * 讀取功能:
 * int read():表示讀取下一個(gè)字節(jié)并返回
 * 輸入流的使用步驟:
 * 1)創(chuàng)建輸入流;
 * 2)讀取數(shù)據(jù);
 * 3)關(guān)閉輸入流;
 */
public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建輸入流的對象 java.io.FileNotFoundException: D:\test1 (拒絕訪問。) 系統(tǒng)找不到指定的文件
        FileInputStream fis = new FileInputStream("D:\\test1\\1.txt");
        //使用輸入流對象調(diào)用read()函數(shù)一次讀一個(gè)字節(jié)數(shù)據(jù)
        //第一次讀取
        int i = fis.read();//i表示讀取的字節(jié)數(shù)據(jù),如果是-1說明文件的結(jié)尾
        //輸出讀取的字節(jié)數(shù)據(jù)
        System.out.println((char)i);
        //第二次讀取
        int i2 = fis.read();
        System.out.println((char)i2);
        //第三次讀取
        int i3 = fis.read();
        System.out.println((char)i3);
        //第四次讀取
        int i4 = fis.read();
        System.out.println((char)i4);
        //第五次讀取
        int i5 = fis.read();
        System.out.println(i5);//-1
        //第六次讀取
        int i6 = fis.read();
        System.out.println(i6);//-1
        //關(guān)閉資源
        fis.close();
    }
}

通過上述代碼發(fā)現(xiàn),在使用輸入流對象fis調(diào)用read()函數(shù)的時(shí)候,出現(xiàn)多次調(diào)用的情況,這樣也會(huì)導(dǎo)致代碼重復(fù),在開發(fā)中盡量簡化代碼的書寫,所以對上述代碼還得進(jìn)一步優(yōu)化:
終極版代碼單個(gè)讀取數(shù)據(jù)的代碼模板如下所示:

   /*
         * 通過書寫代碼發(fā)現(xiàn)上述代碼重復(fù)太多,我們可以考慮使用循環(huán)來解決上述代碼重復(fù)性的問題
         * 問題:循環(huán)的循環(huán)條件是什么?
         * 讀取到文件結(jié)尾,即-1則結(jié)束,所以可以讓讀取的結(jié)果是-1結(jié)束讀取文件
         */
        //讀取數(shù)據(jù)
        int i = fis.read();
        //循環(huán)控制讀取文件數(shù)據(jù)的次數(shù)
        while(i!=-1)
        {
            //說明文件中還有數(shù)據(jù),可以繼續(xù)讀取,輸出讀取的數(shù)據(jù)
            System.out.println((char)i);
            //修改循環(huán)條件 可以理解為一個(gè)光標(biāo),讀取一次,i的值改變一次
            i=fis.read();
          }

2.4.4、復(fù)制文件方式1練習(xí)
需求:每次讀1個(gè)字節(jié)來完成復(fù)制練習(xí)1:
復(fù)制D:\test\1.txt里面的數(shù)據(jù)到F:\2.txt文件中

分析:
數(shù)據(jù)源:一個(gè)文本文件,這里使用字節(jié)流,讀取,所以FileInputStream
目的地:一個(gè)文本文件,這里使用字節(jié)流,寫出,所以FileOutputStream

思路:
A:創(chuàng)建一個(gè)輸入流,關(guān)聯(lián)源文件
B:創(chuàng)建一個(gè)輸出流,關(guān)聯(lián)目標(biāo)文件
C:讀取數(shù)據(jù),讀取硬盤中指定文件中的數(shù)據(jù)內(nèi)容,直到讀取的返回值是-1
D:寫出數(shù)據(jù),將上述每次讀取的數(shù)據(jù)內(nèi)容都寫入到目標(biāo)文件F:\2.txt中
E:釋放資源

package cn.xuexi.inputstream.demo;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
 * 復(fù)制D:\\test\\1.txt里面的數(shù)據(jù)到F:\\2.txt文件中
 * 分析:
 * 數(shù)據(jù)源:D:\\test\\1.txt,使用字節(jié)流,讀入,F(xiàn)ileInputStream
 * 目的地:F:\\2.txt,使用字節(jié)流,寫出,FileOutputStream
 * 思路:
 *  1)創(chuàng)建輸入流對象,關(guān)聯(lián)數(shù)據(jù)源文件;
 *  2)創(chuàng)建輸出流對象,關(guān)聯(lián)目的地文件;
 *  3)讀取數(shù)據(jù),讀取硬盤中指定文件中的數(shù)據(jù),直到讀取文件的末尾處;
 *  4)寫出數(shù)據(jù),將上述每次讀取的文件中的內(nèi)容寫到目標(biāo)文件中;
 *  5)關(guān)閉資源;
 */
public class FileInputStreamTest {
    public static void main(String[] args) throws IOException {
        //1)創(chuàng)建輸入流對象,關(guān)聯(lián)數(shù)據(jù)源文件;
        FileInputStream fis = new FileInputStream("D:\\test\\1.txt");
        //2)創(chuàng)建輸出流對象,關(guān)聯(lián)目的地文件
        FileOutputStream fos = new FileOutputStream("F:\\2.txt");
        //3)讀取數(shù)據(jù),讀取硬盤中指定文件中的數(shù)據(jù),直到讀取文件的末尾處;
        int b=0;
        while((b=fis.read())!=-1)
        {
            //說明數(shù)據(jù)源文件中還有數(shù)據(jù),將每次讀取的數(shù)據(jù)寫到目的地文件中
            fos.write(b);
        }
        //關(guān)閉資源
        fis.close();
        fos.close();
    }
}

2.4.5、每次讀字節(jié)數(shù)組
演示:字節(jié)輸入流,一次讀一個(gè)字節(jié)數(shù)組。
讀取功能:
1)int read(byte[] buf) 讀取數(shù)據(jù)到數(shù)組:buf中,返回的是讀取到的字節(jié)的個(gè)數(shù)len.
定義的字節(jié)數(shù)組是用來存儲(chǔ)從底層文件中讀取到的多個(gè)字節(jié)數(shù)據(jù);
2)在把讀取的字節(jié)個(gè)數(shù)保存在len中。len中保存的是真正給字節(jié)數(shù)組中讀取的字節(jié)個(gè)數(shù),如果讀取到文件末尾,也會(huì)返回-1;
一般這個(gè)數(shù)組的長度都會(huì)定義成1024的整數(shù)倍。
使用循環(huán)重復(fù)的從文件中讀取數(shù)據(jù),每次最多可以從文件中讀取1024個(gè)字節(jié)數(shù)據(jù)
需求:在D:\test\1.txt這個(gè)目錄的1.txt文件中書寫幾個(gè)字符串,如下所示:
hello
world
Java
字樣,然后使用字節(jié)輸入流一次讀一個(gè)字節(jié)數(shù)組來讀取上述路徑中的1.txt文件中的數(shù)據(jù),將每次讀取的數(shù)據(jù)輸出打印到控制臺(tái)。

分析和步驟:
1)創(chuàng)建一個(gè)輸入流對象,和D:\test\1.txt文件進(jìn)行關(guān)聯(lián);
2)定義的字節(jié)byte數(shù)組b,這個(gè)字節(jié)數(shù)組的長度是5,主要是用來存儲(chǔ)從底層文件中讀取到的多個(gè)字節(jié)數(shù)據(jù);
3)用來記錄當(dāng)前給byte數(shù)組中讀取的字節(jié)個(gè)數(shù)的變量,int len = 0;
4)先執(zhí)行fis.read()函數(shù)從底層讀取數(shù)據(jù),然后會(huì)把數(shù)據(jù)保存在我們傳遞的參數(shù)b數(shù)組中。返回值定義一個(gè)int類型的變量len記錄著讀取到字節(jié)數(shù)組中的字節(jié)數(shù);
5)輸出記錄的讀取到字節(jié)數(shù)len和將字節(jié)數(shù)組轉(zhuǎn)換后的字符串?dāng)?shù)據(jù),將字節(jié)轉(zhuǎn)換為字符串可以使用Arrays.toString(b)或者String類的構(gòu)造函數(shù);
6)由于字節(jié)數(shù)組長度是5,所以需要多次讀取,按照上述操作多次讀取1.txt文件中剩余的數(shù)據(jù),將結(jié)果輸出到控制臺(tái)上面;

/*
 * 演示:字節(jié)輸入流,一次讀一個(gè)字節(jié)數(shù)組。
 * int read(byte[] b) 表示定義一個(gè)byte數(shù)組,每次讀取的字節(jié)都存儲(chǔ)到這個(gè)數(shù)組中
 */
public class FileInputStreamDemo1 {
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建輸入流對象,關(guān)聯(lián)源文件
        FileInputStream fis = new FileInputStream("D:\\test\\1.txt");
        //第一次讀取
        //定義一個(gè)數(shù)組保存字節(jié)數(shù)據(jù)
        byte[] b=new byte[5];
        //讀取數(shù)據(jù) 將數(shù)據(jù)保存到字節(jié)數(shù)組中
        int len = fis.read(b);
        //輸出len
        System.out.println("len="+len);
        //輸出字節(jié)數(shù)組中的內(nèi)容
//      System.out.println(Arrays.toString(b));
        System.out.println(new String(b));//hello
         //第一次讀取結(jié)果:
            /* 
              len=5
            hello
            */
          //第二次讀取
         len = fis.read(b);
        //輸出len
        System.out.println("len="+len);
        //輸出字節(jié)數(shù)組中的內(nèi)容
        System.out.println(new String(b));
         //第二次讀取結(jié)果:
        /*
            len=5
            \r\n
            wor
         */

        //第三次讀取
        len = fis.read(b);
        //輸出len
        System.out.println("len="+len);
        //輸出字節(jié)數(shù)組中的內(nèi)容
        System.out.println(new String(b));
         //第三次讀取結(jié)果:
        /*
         *  len=5
            ld\r\n
            J
         */
        //第四次讀取
        len = fis.read(b);
        //輸出len
        System.out.println("len="+len);
        //輸出字節(jié)數(shù)組中的內(nèi)容
        System.out.println(new String(b));
         //第四次讀取結(jié)果:
        /*
         在1.txt文件中,如果最后的數(shù)據(jù)Java有回車換行,那么會(huì)輸出如下所示數(shù)據(jù):
            len=5
            ava\r\n
         在1.txt文件中,如果最后的數(shù)據(jù)Java沒有回車換行,那么會(huì)輸出如下所示數(shù)據(jù):
             len=3
            ava
            J 
         */
    }
}

說明:
1)通過上述代碼發(fā)現(xiàn)返回值len表示讀取到的字節(jié)數(shù),而不是字節(jié)數(shù)組的長度。如果讀取為5個(gè)字節(jié)數(shù),那么返回5,即,len等于5。如果讀取到的字節(jié)數(shù)是3,那么返回3,即len等于3。如果文件中沒有要讀取的數(shù)據(jù),則返回-1。
2)上述代碼中當(dāng)?shù)谒拇巫x取的時(shí)候有問題,如果文件中最后一個(gè)數(shù)據(jù)后面沒有回車換行,那么應(yīng)該只打印ava,為什么會(huì)打印:
ava
J
呢?
原因如下圖所示:


13.png

為了解決上述代碼出現(xiàn)的問題,我們更希望看到當(dāng)我們讀取幾個(gè)字節(jié)數(shù)據(jù),我們就輸出幾個(gè)字節(jié)數(shù)據(jù),所以這里我們不能在使用new String(b)構(gòu)造函數(shù),我們應(yīng)該使用new String(b,int offset,int length)構(gòu)造函數(shù),這樣做就不會(huì)出現(xiàn)上述問題。

說明:new String(b,0,len):
       創(chuàng)建一個(gè)字符串對象,把b數(shù)組中的數(shù)據(jù)轉(zhuǎn)成字符串,從0位置開始,共計(jì)轉(zhuǎn)len個(gè)。
       從0位置開始,因?yàn)槊看握{(diào)用輸入流的read方法的時(shí)候,把數(shù)據(jù)給byte數(shù)組中保存
       這時(shí)真正是從byte數(shù)組的0位置開始存儲(chǔ)讀取的每個(gè)字節(jié)數(shù)據(jù)。
       len是byte數(shù)組中的保存的真正的讀取的字節(jié)個(gè)數(shù)。

這樣做就可以做到我們讀取幾個(gè)字節(jié)數(shù)據(jù),我們就輸出幾個(gè)字節(jié)數(shù)據(jù)的目的。

代碼如下所示:

public class FileInputStreamDemo1 {
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建輸入流對象,關(guān)聯(lián)源文件
        FileInputStream fis = new FileInputStream("D:\\test\\1.txt");
        //第一次讀取
        //定義一個(gè)數(shù)組保存字節(jié)數(shù)據(jù)
        byte[] b=new byte[5];
        //讀取數(shù)據(jù) 將數(shù)據(jù)保存到字節(jié)數(shù)組中
        int len = fis.read(b);
//      System.out.println(new String(b,0,len));//hello
        System.out.print(new String(b,0,len));//hello
        //第二次讀取
         len = fis.read(b);
        //輸出len
//      System.out.println("len="+len);
        //輸出字節(jié)數(shù)組中的內(nèi)容
//      System.out.println(new String(b,0,len));
        System.out.print(new String(b,0,len));
        //第三次讀取
        len = fis.read(b);
        //輸出len
//      System.out.println("len="+len);
        //輸出字節(jié)數(shù)組中的內(nèi)容
//      System.out.println(new String(b,0,len));
        System.out.print(new String(b,0,len));
        //第四次讀取
        len = fis.read(b);
        //輸出len
//      System.out.println("len="+len);
        //輸出字節(jié)數(shù)組中的內(nèi)容
//      System.out.println(new String(b,0,len));
        System.out.print(new String(b,0,len));
         //關(guān)閉資源
        fis.close();
      }
}

上面的代碼重復(fù)太多了,考慮用循環(huán)。

問題來了:循環(huán)的接收條件是什么呢?
結(jié)束條件:末尾返回-1
終極版代碼如下所示:

//定義一個(gè)數(shù)組
//      byte[] b=new byte[5];
        //終極版代碼模板
        byte[] b=new byte[1024];//數(shù)組長度一般是1024的整數(shù)倍
        //定義一個(gè)變量保存讀取字節(jié)的個(gè)數(shù)
        int len=0;
        //fis.read(b)表示讀取的數(shù)據(jù)都存放到byte數(shù)組中了,len表示讀取字節(jié)數(shù)
        while((len=fis.read(b))!=-1)//一定要傳遞參數(shù)數(shù)組b
        {
            System.out.print(new String(b,0,len));
        }
         //關(guān)閉資源
        fis.close();

2.4.6、復(fù)制文件方式2練習(xí):
需求:每次讀字節(jié)數(shù)組來完成復(fù)制練習(xí)2:
復(fù)制D:\test\1.txt中的數(shù)據(jù)到F:\2.txt文件中。

分析和步驟:
1)創(chuàng)建輸入流,關(guān)聯(lián)源文件D:\test\1.txt;
2)創(chuàng)建輸出流,關(guān)聯(lián)目標(biāo)文件F:\2.txt;
3)讀寫數(shù)據(jù);
4)關(guān)閉資源;

/*
 * 需求:每次讀字節(jié)數(shù)組來完成復(fù)制練習(xí)2:
    復(fù)制D:\\test\\1.txt到F:\\2.txt
 */
public class FileInputStreamTest1 {
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建輸入流對象,關(guān)聯(lián)源文件
        FileInputStream fis = new FileInputStream("D:\\test\\1.txt");
        //創(chuàng)建輸出流對象,關(guān)聯(lián)目標(biāo)文件
        FileOutputStream fos = new FileOutputStream("F:\\2.txt");
        //讀取數(shù)據(jù)
        byte[] b=new byte[1024];
        int len=0;
        while((len=fis.read(b))!=-1)
        {
            //寫數(shù)據(jù)
            fos.write(b, 0, len);
        }
        //關(guān)閉資源
        fis.close();
        fos.close();
    }
}

注意:
1)使用InputStream類中的read()函數(shù)表示每次讀單個(gè)字節(jié),然后使用OutputStream類中的write(int b)函數(shù)把讀出來的字節(jié)寫入到新的文件中;
2)使用InputStream類中的read(byte[] b)函數(shù)表示每次讀字節(jié)數(shù)組,然后使用OutputStream類中的write(byte[] b,int off,int len)函數(shù)把讀出來的字節(jié)數(shù)組中的數(shù)據(jù)寫入到新的文件中;
上述兩種辦法雖然都可以實(shí)現(xiàn),但是是有區(qū)別的,第二種字節(jié)數(shù)組效率要高,開發(fā)中優(yōu)先考慮第二種,兩種讀寫文件方式的區(qū)別如下圖所示:


14.png

復(fù)制文件的時(shí)候注意:
1、 需要定義中間的變量(容器),讓字節(jié)輸入流和輸出流可以聯(lián)系起來。然后輸入流讀取數(shù)據(jù),輸出流把讀取到的數(shù)據(jù)寫到文件中。
2、 在讀取數(shù)據(jù)的時(shí)候,調(diào)用read() 和read( 字節(jié)數(shù)組 )有嚴(yán)格的區(qū)別。
3、 在寫出數(shù)據(jù)的時(shí)候,write(字節(jié)數(shù)組) 和 write( 字節(jié)數(shù)組,0 ,長度 )有區(qū)別。

2.5、復(fù)制文件綜合練習(xí)

需求:
演示復(fù)制文件:把D盤下的1.mp3 拷貝到F盤下的1.mp3。

分析和步驟: 每次讀1個(gè)字節(jié)來完成復(fù)制
1)創(chuàng)建測試類CopyFile,在這個(gè)類中創(chuàng)建兩個(gè)函數(shù)method_1和method_2;
2)在method_1()函數(shù)中演示復(fù)制文件一次讀寫一個(gè),在這個(gè)函數(shù)中創(chuàng)建輸入流對象fis和輸出流對象fos,分別指定源文件位置D:\1.mp3和目標(biāo)文件位置F:\1.mp3;
3)獲得開始復(fù)制的系統(tǒng)時(shí)間的毫秒值start;
4)使用while循環(huán)控制讀文件的次數(shù),使用輸入流對象fis調(diào)用read()函數(shù)一次一次讀,使用輸出流對象fos調(diào)用write()函數(shù)一次一次寫;
5)獲取復(fù)制結(jié)束的時(shí)間end;
6)結(jié)束時(shí)間end減去開始復(fù)制的時(shí)間start,關(guān)閉輸入和輸出流;

//演示一次讀取一個(gè)字節(jié)
    public static void method_1() throws IOException {
        //創(chuàng)建輸入流對象 關(guān)聯(lián)源文件
        FileInputStream fis = new FileInputStream("D:\\1.mp3");
        //創(chuàng)建輸出流對象 關(guān)聯(lián)目標(biāo)文件
        FileOutputStream fos=new FileOutputStream("F:\\1.mp3");
        //獲取開始復(fù)制的時(shí)間
        long start = System.currentTimeMillis();
        //定義變量保存字節(jié)
        int ch=0;
        while((ch=fis.read())!=-1)
        {
            //寫數(shù)據(jù)
            fos.write(ch);
        }
        //獲取結(jié)束的時(shí)間
        long end = System.currentTimeMillis();
        System.out.println("復(fù)制時(shí)間是:"+(end-start));//復(fù)制時(shí)間是:38406
        //關(guān)閉資源
        fis.close();
        fos.close();
    }

需求:
演示復(fù)制文件:把D盤下的1.mp3 拷貝到F盤下。

分析和步驟:每次讀字節(jié)數(shù)組來完成復(fù)制
1)步驟和上述步驟大致相同,只是需要定義一個(gè)整數(shù)變量len來記錄當(dāng)前給byte數(shù)組中讀取的字節(jié)個(gè)數(shù)的;
2)定義的字節(jié)數(shù)組buf是用來存儲(chǔ)從底層文件中讀取到的多個(gè)字節(jié)數(shù)據(jù);
3)使用read(buf)函數(shù)讀取文件的時(shí)候需要傳入一個(gè)數(shù)組參數(shù)buf;
4)每讀取一次使用輸出流對象調(diào)用write()函數(shù)向文件中寫數(shù)據(jù);

//演示一次讀取一個(gè)字節(jié)數(shù)組
    public static void method_2() throws IOException {
        // 創(chuàng)建輸入流對象 關(guān)聯(lián)源文件
        FileInputStream fis = new FileInputStream("D:\\1.mp3");
        //創(chuàng)建輸出流對象 關(guān)聯(lián)目標(biāo)文件
        FileOutputStream fos = new FileOutputStream("F:\\1.mp3");
        //獲取開始復(fù)制的時(shí)間
        long start = System.currentTimeMillis();
        //定義數(shù)組  讀數(shù)據(jù)
//      byte[] b=new byte[8192];
        byte[] b=new byte[1024];
        int len=0;
        while((len=fis.read(b))!=-1)
        {
            //寫數(shù)據(jù)
            fos.write(b, 0, len);
        }
        //獲取結(jié)束復(fù)制的時(shí)間
        long end = System.currentTimeMillis();
        System.out.println("復(fù)制時(shí)間是:"+(end-start));//復(fù)制時(shí)間是:15
        //關(guān)閉資源
        fis.close();
        fos.close();
        
    }

2.6、總結(jié)輸入流讀取文件的模版代碼(掌握)

1、一次讀取一個(gè)字節(jié)的模版代碼
          1)、創(chuàng)建輸入流對象
                 FileInputStream  fis = new FileInputStream( “文件” );
注意:
1)FileInputStream類的構(gòu)造函數(shù)的參數(shù)一定要是個(gè)文件路徑,不能是文件夾路徑,因?yàn)镕ileInputStream類是用來操作文件的,是讀取文件中的內(nèi)容;
2)在構(gòu)造函數(shù)中指定的文件在硬盤上一定要存在,否則會(huì)拋異常;
          2)、定義變量,記錄每次讀取到的字節(jié)數(shù)據(jù)
                 int b= 0;
          3)、使用循環(huán)讀取數(shù)據(jù)
                 while(  ( b = fis.read() ) !=-1  ){
       //處理讀取到的數(shù)據(jù),數(shù)據(jù)在b中。
     }
          4)、關(guān)閉流對象
                 fis.close(); 
2、一次讀取多個(gè)字節(jié)數(shù)據(jù)的模版代碼(掌握)
          1)、創(chuàng)建輸入流對象
                 FileInputStream  fis = new FileInputStream( “文件” ); 
          2)、定義變量,記錄讀取到的字節(jié)個(gè)數(shù)
                 int len = 0;         
          3)、定義數(shù)組保存讀取到的多個(gè)字節(jié)數(shù)據(jù)
                 byte[] buf = newbyte[1024*n];            
          4)、使用循環(huán)讀取數(shù)據(jù)
                     while(  (  len= fis.read( buf ) ) !=-1 ){
             //處理讀取到的數(shù)據(jù),數(shù)據(jù)在buf中,buf中共計(jì)有效的數(shù)據(jù)是len個(gè)
           }
          5)、關(guān)閉流對象
                 fis.close(); 

2.7、IO流中的異常處理模版(了解)

我們使用Java程序,操作的是Java程序以外的其他設(shè)備上的數(shù)據(jù),都有可能發(fā)生異常問題。
我們在書寫的Java程序讀寫其他設(shè)備上的數(shù)據(jù)時(shí),都要考慮異常問題。這些異常一般開發(fā)中都要開發(fā)者自己處理掉,不能直接往出拋。

說明:
1)對于輸入流FileInputStream來說,硬盤上一定要有文件,否則會(huì)報(bào)找不到文件異常;對于輸入流read()函數(shù)來說,假設(shè)有文件,但是文件沒有可讀的權(quán)限,那么對于輸入流對象調(diào)用read()函數(shù)也會(huì)報(bào)異常;對于關(guān)閉流的函數(shù)close()來說,也同樣有異常;
2)對于輸出流FileOutputStream來說,指定的路徑一定是硬盤上的文件不能是文件夾,否則會(huì)報(bào)異常;對于輸入流write()函數(shù)來說,假設(shè)有文件,但是文件沒有可寫的權(quán)限,那么對于輸出流對象調(diào)用write()函數(shù)也會(huì)報(bào)異常;對于關(guān)閉流的函數(shù)close()來說,也同樣有異常;
3)流的關(guān)閉動(dòng)作,必須被執(zhí)行。但是如果在流關(guān)閉之前,已經(jīng)出異常,那么流的關(guān)閉動(dòng)作無法執(zhí)行。
必須被執(zhí)行的動(dòng)作,放到finally中;把關(guān)閉動(dòng)作,放到finally代碼塊中后,流的定義也必須放到try塊的外面,否則看不到;在finally中關(guān)閉流的時(shí)候要先判斷流是否為null,如果不判斷那么流對象調(diào)用close()函數(shù)有可能報(bào)空指針異常。

IO流讀寫數(shù)據(jù)的模版代碼:
分析和步驟:
1)定義一個(gè)測試類IOExceptionDemo 在這個(gè)類中定義兩個(gè)函數(shù)method_1()和method_2()分別書寫讀數(shù)據(jù)和寫數(shù)據(jù)的模版處理代碼;
2)在method_1()函數(shù)中定義一個(gè)FileInputStream 類的對象fis=null;
3)在try-catch-finally代碼塊中創(chuàng)建FileInputStream 類的對象并指定讀數(shù)據(jù)的文件D:\1.txt;
4)使用一次性讀取一個(gè)字節(jié)數(shù)據(jù)來完成讀數(shù)據(jù);
5)在finally代碼塊中在關(guān)閉流之前要先判斷流對象是否存在;
6)在method_2()函數(shù)中定義一個(gè)FileOutputStream 類的對象fos=null;
7)在try-catch-finally代碼塊中創(chuàng)建FileOutputStream 類的對象并指定讀數(shù)據(jù)的文件D:\2.txt;
8)使用輸出流對象fos調(diào)用write()函數(shù)向指定的文件中寫數(shù)據(jù);
9)在finally代碼塊中在關(guān)閉流之前要先判斷流對象是否存在;

/*
 * 字節(jié)流處理異常代碼模板
 */
public class IOExceptionDemo {
    public static void main(String[] args) {
        method_2();
    }
    //讀數(shù)據(jù)的處理異常的模板代碼
    public static void method_1() {
        FileInputStream fis=null;
        try {
            //創(chuàng)建輸入流對象
            //這里的fis對象只能在try的大括號(hào)里面使用,不能在其他地方使用
            fis = new FileInputStream("D:\\test\\1.txt");
            //定義數(shù)組
            byte[] b=new byte[1024];
            //定義變量
            int len=0;
            while((len=fis.read(b))!=-1)
            {
                System.out.println(new String(b,0,len));
            }
        } catch (IOException e) {
            System.out.println("讀數(shù)據(jù)異常了");
            //后期在寫項(xiàng)目的時(shí)候,抓到異常了,這里需要寫日志文檔
        }finally
        {
            if(fis!=null)
            {
                try {
                    //不管程序是否有異常都得需要關(guān)閉資源
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //寫數(shù)據(jù)的處理異常的模板代碼
    public static void method_2() {
        FileOutputStream fos=null;
        try {
            // 創(chuàng)建輸出流對象
            fos = new FileOutputStream("D:\\test\\1.txt");
            fos.write(97);
        } catch (IOException e) {
            System.out.println("寫數(shù)據(jù)異常了");
        }finally
        {
            if(fos!=null)
            {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.8、字節(jié)流小節(jié)

1、字節(jié)輸出流:
     A:使用輸出流給文件中寫數(shù)據(jù)的時(shí)候,如果文件不存在,會(huì)創(chuàng)建這個(gè)文件,如果文件存在,會(huì)覆蓋掉文件中原有的數(shù)據(jù)。
     B:如果不想覆蓋掉原來文件中的數(shù)據(jù),在創(chuàng)建輸出流的時(shí)候,可以指定布爾值為true,這樣就會(huì)在文件的末尾追加數(shù)據(jù)。
可以使用構(gòu)造函數(shù)FileOutputStream(String name, boolean append)來實(shí)現(xiàn)。
    C:write( int b ) 它只能寫出這個(gè)int數(shù)據(jù)中最低位的1個(gè)字節(jié)數(shù)據(jù)。
     D:換行
    //獲得系統(tǒng)中的行分隔符
    Stringseparator = System.getProperty("line.separator");
     //向文件中寫出數(shù)據(jù)
     fos.write(("黑旋風(fēng)真帥哈哈"+separator).getBytes()); 

2、字節(jié)輸入流:
   它不會(huì)創(chuàng)建文件,同時(shí)要求輸入流關(guān)聯(lián)的一定是文件,不能是文件夾。
   字節(jié)輸入流有自己的讀取數(shù)據(jù)的模版代碼。
   一次讀取一個(gè)字節(jié)數(shù)據(jù),或一次讀取多個(gè)字節(jié)數(shù)據(jù)。
 3、流的操作:
   不管是輸入流還是輸出流,在操作完之后都要關(guān)閉流。 

2.9、字節(jié)流緩沖區(qū)介紹(了解)

通過以上演示發(fā)現(xiàn),一次讀很多字節(jié)比一次讀一個(gè)字節(jié)快。Java的設(shè)計(jì)者肯定知道這一點(diǎn)。
Java專門提供了一類流,可以實(shí)現(xiàn)高效的讀取。這種流,就是在內(nèi)部維護(hù)了一個(gè)緩沖區(qū)數(shù)組。
字節(jié)流緩沖區(qū):包括字節(jié)輸入流緩沖區(qū)和字節(jié)輸出流緩沖區(qū)。
字節(jié)輸入流緩沖區(qū):
BufferedInputStream:

15.png

說明:
BufferedInputStream:它就是一個(gè)緩沖區(qū),它內(nèi)部維護(hù)一個(gè)數(shù)組,可以從底層讀取多個(gè)數(shù)據(jù)。

16.png

構(gòu)造方法摘要
BufferedInputStream(InputStream in)
創(chuàng)建一個(gè)新的緩沖輸入流,用來將數(shù)據(jù)寫入指定的底層輸入流。

問題:為什么緩沖區(qū)流需要接收一個(gè)普通字節(jié)流呢?
緩沖區(qū)流是為了 高效而設(shè)計(jì)的,緩沖區(qū)流本身僅僅是維護(hù)了一個(gè)數(shù)組。不具備讀和寫的功能。真正的讀寫還是要依賴普通的字節(jié)流。

緩沖區(qū)字節(jié)流和以前字節(jié)流讀寫文件的做法區(qū)別如下圖所示:


17.png

說明:
1)使用緩沖區(qū)輸入流的時(shí)候,后期我們需要數(shù)據(jù)的時(shí)候,不直接和文件進(jìn)行交互,而是向緩沖區(qū)索要數(shù)據(jù);
2)緩沖區(qū)只能臨時(shí)存儲(chǔ)數(shù)據(jù),它不能從底層讀取數(shù)據(jù),從底層讀取數(shù)據(jù)還得需要輸入流;
3)使用緩沖區(qū)的目的只是為了提高讀寫的效率,先使用FileInputStream輸入流將硬盤上的文件讀取到緩沖區(qū)中,然后在從緩沖區(qū)中的數(shù)組中取出數(shù)據(jù)存放到我們之前定義好的數(shù)組中,因?yàn)閮蓚€(gè)數(shù)組都是在內(nèi)存中,這樣交互數(shù)據(jù)會(huì)更快一點(diǎn);
4)而我們之前都是從硬盤上直接將數(shù)據(jù)讀取到定義好的數(shù)組中,這樣效率會(huì)低點(diǎn);

分析和步驟:
1)定義一個(gè)測試類BufferedInputStreamDemo;
2)在這個(gè)類中的main函數(shù)中創(chuàng)建一個(gè)可以直接和文件交互的輸入流對象in,并指定路徑D:\test\123.txt;
3)創(chuàng)建一個(gè)緩沖區(qū)對象bufIn,需要指定可以從底層讀取數(shù)據(jù)的流對象;
4)創(chuàng)建一個(gè)byte類型的數(shù)組buf,大小是1024,定義一個(gè)整數(shù)變量len=0;
5)使用緩沖區(qū)對象bufIn調(diào)用read()函數(shù)讀取數(shù)據(jù)并存儲(chǔ)到定義好的數(shù)組buf中,并轉(zhuǎn)換為字符串,輸出;
6)關(guān)閉流;

/*
 * 演示字節(jié)輸入流緩沖區(qū)
 */
public class BufferedInputStreamDemo {
    public static void main(String[] args) throws IOException {
        //創(chuàng)建一個(gè)可以直接和文件進(jìn)行交互的輸入流對象
        FileInputStream fis = new FileInputStream("D:\\test\\123.txt");
        //創(chuàng)建輸入流緩沖區(qū),指定可以從底層讀取數(shù)據(jù)的流對象
        BufferedInputStream bufin = new BufferedInputStream(fis);
        //創(chuàng)建數(shù)組
        byte[] b=new byte[1024];
        //定義一個(gè)變量
        int len=0;
        while((len=bufin.read(b))!=-1)
        {
            System.out.println(new String(b,0,len));
        }
        //關(guān)閉資源
        bufin.close();
    }
}

說明:上述代碼關(guān)閉緩沖區(qū)流就可以了,不用手動(dòng)關(guān)閉字節(jié)流了,因?yàn)樵诘讓右呀?jīng)關(guān)閉字節(jié)流了。

字節(jié)輸出流緩沖區(qū):
BufferedOutputStream:


18.png

BufferedOutputStream:它可以把需要寫出的數(shù)據(jù),寫到自己的緩沖區(qū)中,當(dāng)緩沖區(qū)寫滿,或者我們手動(dòng)調(diào)用flush方法,或者最后我們關(guān)流,才會(huì)把緩沖區(qū)中的數(shù)據(jù)一次性的寫到底層文件中。

構(gòu)造方法摘要:
BufferedOutputStream(OutputStreamout)
創(chuàng)建一個(gè)新的緩沖輸出流,以將數(shù)據(jù)寫入指定的底層輸出流。

問題:為什么緩沖區(qū)流需要接收一個(gè)普通字節(jié)流呢?
緩沖區(qū)流是為了 高效而設(shè)計(jì)的,緩沖區(qū)流本身僅僅是維護(hù)了一個(gè)數(shù)組。不具備讀和寫的功能。真正的讀寫還是要依賴普通的字節(jié)流。

分析和步驟:
1)定義一個(gè)測試類BufferedOutputStreamDemo;
2)在這個(gè)類中的main函數(shù)中創(chuàng)建一個(gè)可以直接和文件交互的輸出流對象fos,并指定路徑D:\out.txt;
3)創(chuàng)建一個(gè)緩沖區(qū)輸出流對象bufOut;
4)使用緩沖區(qū)輸出流對象bufOut調(diào)用write()函數(shù)向指定文件中寫入指定的內(nèi)容;
5)使用緩沖區(qū)輸出流對象bufOut調(diào)用close()函數(shù)關(guān)閉緩沖區(qū)輸出流,這樣就可以將指定的數(shù)據(jù)寫入到指定的文件中;

/*
 * 演示字節(jié)輸出流緩沖區(qū)
 */
public class BufferedOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        //創(chuàng)建可以和底層文件交互的流對象
        FileOutputStream fos = new FileOutputStream("D:\\test\\out.txt");
        //創(chuàng)建輸出流緩沖區(qū)對象
        BufferedOutputStream buffOut = new BufferedOutputStream(fos);
        //寫數(shù)據(jù)
        buffOut.write("hello 狗哥哈哈".getBytes());
        //關(guān)閉緩沖區(qū)輸出流
        //buffOut.close();
         //將緩沖區(qū)中的數(shù)據(jù)刷新到輸出流中
         buffOut.flush();
    }
}

注意:
flush:刷新緩沖區(qū),把緩沖區(qū)中的有效數(shù)據(jù)刷到底層文件中,刷新完成之后,緩沖區(qū)流還可以繼續(xù)使用。
close:關(guān)閉流和底層文件之間的關(guān)聯(lián),一旦流關(guān)閉了,就不能在使用

小結(jié):
字節(jié)流緩沖區(qū)對象屬于IO流中的高效流,可以提高文件的讀寫效率。

緩沖區(qū)對象讀寫的原理:
BufferedInputStream對象:該流需要使用輸入流對象讀取字節(jié)數(shù)據(jù),把讀取到字節(jié)數(shù)據(jù)暫時(shí)存儲(chǔ)在緩沖區(qū)對象內(nèi)部的數(shù)組(緩沖區(qū))中,當(dāng)內(nèi)部的緩沖區(qū)數(shù)組存滿了后或不需要再讀取數(shù)據(jù)了,就可以從內(nèi)部的緩沖區(qū)數(shù)組中獲取字節(jié)數(shù)據(jù)。
BufferedOutputStream對象:該流需要使用輸出流對象寫入字節(jié)數(shù)據(jù),把要寫入的數(shù)據(jù)轉(zhuǎn)換為字節(jié)數(shù)據(jù)后暫時(shí)存儲(chǔ)到緩沖區(qū)數(shù)組中,當(dāng)內(nèi)部緩沖區(qū)數(shù)組存滿后或者關(guān)閉流資源或者刷新緩沖區(qū)時(shí)就可以從緩沖區(qū)數(shù)組取出字節(jié)數(shù)據(jù)寫入到文件中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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