1. 程式人生 > >Java I/O流複習(二)—字元流、字元緩衝流、以及和位元組流的區別

Java I/O流複習(二)—字元流、字元緩衝流、以及和位元組流的區別

Java I/O流複習(二)

1 字元輸入流

字元流(Java IO的Reader和Writer)功能與InputStream和OutputStream非常類似,InputStream和OutputStream基於位元組處理,而字元流(Reader和Writer)是基於字元處理。主要用於讀寫文字。

1.1 Reader 類的常用方法

Reader類是Java IO中所有Reader的基類。子類包括FileReader,BufferedReader,InputStreamReader,StringReader和其他Reader。

  1. read() ; 讀取字元輸入流。讀取字元輸入流的下一個字元,返回一個字元,意味著這個返回值的範圍在0到65535之間(當達到流末尾時,同樣返回-1)。這並不意味著Reader只會從資料來源中一次讀取2個位元組,Reader會根據文字的編碼,一次讀取一個或者多個位元組。
  2. read(char cbuf[]);讀取字元輸入流。讀取多個字元,存入字元陣列cbuf,返回實際讀入的字元數。
  3. read(char cbuf[], int off, int len); 方法,讀取字元輸入流。每次讀取len個字元,存入字元陣列cbuf,從off下標開始儲存。
  4. close(); 關閉當前流,釋放與該流相關的資源,防止資源洩露。在帶資源的try語句中將被自動呼叫。關閉流之後還試圖讀取字元,會出現IOException異常。

1.2 Reader類的子類:FileReader

FileReader類從InputStreamReader類繼承而來(間接繼承了Reader)。該類按字元讀取流中資料。

1.3 FileReader構造方法和常用方法

1.3.1 構造方法

  1. FileReader(File file);通過開啟一個到實際檔案的連線來建立一個FileReader,該檔案通過檔案系統中的 File 物件 file 指定。

  2. FileReader(String fileName) ; 通過開啟一個到實際檔案的連線來建立一個FileReader,該檔案通過檔案系統中的路徑名 name 指定。

  3. (瞭解)FileReader(FileDescriptor fd) ; 在給定從中讀取資料的FileDescriptor 的情況下建立一個新 FileReader。

    提示:FileDescriptor 是“檔案描述符”。

    其中有三個屬性:

    1. in 標準輸入(鍵盤)的描述符(從鍵盤輸入讀取流)
    2. out 標準輸出(螢幕)的描述符(講流輸出到控制檯上)
    3. err 標準錯誤輸出(螢幕)的描述符(將流以紅色的字型輸出到控制檯上)

    程式碼示例:

    try {
       FileWriter fw = new FileWriter(FileDescriptor.out);
       fw.write("我是愛你的。");
       fw.flush();
       fw.close();
    }...

    控制檯輸出:

1.3.2 常用方法

test.txt 檔案內容(字元長度為17)

  1. read();讀取字元輸入流。讀取字元輸入流的下一個字元,返回一個字元。

    try {
    File file = new File("test.txt");
    FileReader fileReader = new FileReader(file);
    int read = fileReader.read();//預設第一次讀取第一個字元
    System.out.println((char)read);
    }...

    結果:

  2. read(char cbuf[]);讀取字元輸入流。讀取多個字元,存入字元陣列cbuf,返回實際讀入的字元數。

    try {
    File file = new File("test.txt");
    FileReader fileReader = new FileReader(file);
    
    char c [] = new char[20];
    int len = fileReader.read(c);//
    System.out.println("讀取的字元長度為:"+len);
    
    for (char d : c) {
        System.out.print(d);
    }
    }...

    結果:

  3. read(char cbuf[], int off, int len);讀取字元輸入流。每次讀取len個字元,存入字元陣列cbuf,從off下標開始儲存。

    try {
    File file = new File("test.txt");
    FileReader fileReader = new FileReader(file);
    
    char c [] = new char[20];
    int len = fileReader.read(c,2,8);//讀取8個字元存入c陣列,從下標2開始儲存
    System.out.println("讀取的字元長度為:"+len);
    
    for (char d : c) {
        System.out.print(d);
    }
    
    }

    結果:

  4. close();關閉當前流,釋放與該流相關的資源,防止資源洩露。在帶資源的try語句中將被自動呼叫。關閉流之後還試圖讀取字元,會出現IOException異常。

    try {
    File file = new File("test.txt");
    FileReader fileReader = new FileReader(file);
    int read = fileReader.read();//
    System.out.println((char)read);
    fileReader.close();//通過close()來關閉流,以釋放系統資源。
    }...
    //或者在這裡關閉
     ...finally {
        if(fileReader!=null)
            fileReader.close();
     }

    注意:

    1. 通常不使用close會導致記憶體洩露,垃圾回收機制會回收,但是最好自己顯式關閉
    2. OutputStream的作用是如FileOutStream,當不呼叫close的時候,不會將快取刷入檔案中。

    所以:一般使用完IO流之後都要通過close()來關閉,以釋放系統資源

2 字元輸出流

2.1 Writer類的常用方法

  1. write (String str); 將指定的字串寫入此輸出流。
  2. write(char[] cbuf, int off, int len); 將指定 char 陣列中從偏移量 off 開始的 len 個字元寫入此輸出流。
  3. flush(); 用於清空快取裡的資料,並通知底層去進行實際的寫操作。(強制把快取區裡面的資料寫入到檔案)
  4. close();關閉當前流,釋放與該流相關的資源。

2.2 Writer類的子類:FileWriter

FileWriter類從OutputStreamWriter類繼承而來(間接繼承Writer類)。該類按字元向流中寫入資料。

2.3 FileWriter 構造方法和常用方法

2.3.1 構造方法

  1. FileWriter(File file);通過開啟一個到實際檔案的連線來建立一個FileWriter,該檔案通過檔案系統中的 File 物件 file 指定。
  2. FileWriter(File file, boolean append);通過開啟一個到實際檔案的連線來建立一個FileWriter,該檔案通過檔案系統中的 File 物件 file 指定。 如果第二個引數為true,則將字元寫入檔案末尾處,而不是寫入檔案開始處。
  3. FileWriter(String fileName);通過開啟一個到實際檔案的連線來建立一個FileWriter,該檔案通過檔案系統中的路徑名 name 指定。
  4. FileWriter(String fileName, boolean append);通過開啟一個到實際檔案的連線來建立一個FileWriter,該檔案通過檔案系統中的路徑名 name 指定。如果第二個引數為true,則將字元寫入檔案末尾處,而不是寫入檔案開始處。
  5. FileWriter(FileDescriptor fd);在給定從中寫入資料的FileDescriptor 的情況下建立一個新 FileReader。(可以向控制檯輸出文字流)。

2.3.2 常用方法

  1. write (String str); 將指定的字串寫入此輸出流。

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    fileWriter.write("十年之前,我不認識你。");
    fileWriter.flush();
    fileWriter.close();
    }...

    結果:

  2. write(int c );將指定的字元寫入此輸出流。

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    fileWriter.write('育');
    fileWriter.flush();
    fileWriter.close();
    }...

    結果:

  3. write(char[] cbuf);將 cbuf 字元陣列寫入此輸出流。

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    char[] charArray = "字串轉字元陣列".toCharArray();
    fileWriter.write(charArray);
    fileWriter.flush();
    fileWriter.close();
    }

    結果:

  4. write(char[] cbuf, int off, int len);將 cbuf 字元陣列,按偏移量 off 開始的 len 個字元寫入此輸出流。

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    char[] charArray = "字串轉字元陣列".toCharArray();
    fileWriter.write(charArray, 1, 5);//從偏移量 1 開始,寫入5個字元。
    fileWriter.flush();
    fileWriter.close();
    }

    結果:

  5. write(String str, int off, int len);

    try {
    File file = new File("test.txt");
    Writer fileWriter = new FileWriter(file);
    String str ="字串也可以制定寫的內容";
    fileWriter.write(str, 3, 5);
    fileWriter.flush();
    fileWriter.close();
    } 

    結果:

3 字元快取流(BufferedReader/BufferedWriter)

(BufferedReader/BufferedWriter) 是帶緩衝區的字元流,預設緩衝區大小是8Kb,能夠減少訪問磁碟的次數,提高檔案讀取效能;並且可以一次性讀取一行字元。(類似管道套管道一樣,不帶緩衝的流只能一滴一滴流,套了管道後,可以讓一滴一滴留到外面的管道後一次性流出。)

3.1 字元快取流構造方法

3.1.1 BufferedReader

  1. BufferedReader(Reader in);建立一個預設緩衝區大小 8Kb 的字元緩衝輸入流;
  2. BufferedReader(Reader in, int sz);建立一個字元緩衝輸入流;並分配 sz/byte 大小的緩衝區。

3.1.1 BufferedWriter

  1. BufferedWriter(Writer out); 建立一個預設緩衝區大小 8Kb 的字元緩衝輸出流;
  2. BufferedWriter(Writer out, int sz); 建立一個字元緩衝輸出流;並分配 sz/byte 大小的緩衝區。

3.2 字元快取流的常用方法:readLine(), newLine()

  1. BufferedReader.readLine();在字元緩衝輸入流讀取字元的時候,可以一次性讀取一行,並將遊標指向下一行。

    try {
    File file = new File("test.txt");
    FileReader fr = new FileReader(file);
    BufferedReader br = new BufferedReader(fr);
    String str;
    while ((str = br.readLine())!=null) {
        System.out.println(str);
    }
    }
  2. BufferedWriter.newLine();在字串緩衝輸出流寫入字元的時候,預設是在一行寫入,當需要換行的時候,呼叫 newLine() 實現文字換行。

    try {
    File file = new File("test.txt");
    FileWriter fw = new FileWriter(file);
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write("寫入一行的文字");
    bw.newLine();//換行
    bw.write("寫入第二行的文字");
    bw.flush();//重新整理緩衝區,強制寫入檔案中
    bw.close();
    }...

4.位元組流與字元流的區別

位元組流和字元流使用是非常相似的,那麼除了操作程式碼的不同之外,還有哪些不同呢?

位元組流在操作的時候本身是不會用到緩衝區(記憶體)的,是與檔案本身直接操作的,而字元流在操作的時候是使用到緩衝區的

位元組流在操作檔案時,即使不關閉資源(close方法),檔案也能輸出,但是如果字元流不使用close方法的話,則不會輸出任何內容,說明字元流用的是緩衝區,並且可以使用flush方法強制進行重新整理緩衝區,這時才能在不close的情況下輸出內容

那開發中究竟用位元組流好還是用字元流好呢?

在所有的硬碟上儲存檔案或進行傳輸的時候都是以位元組的方法進行的,包括圖片也是按位元組完成,而字元是隻有在記憶體中才會形成的,所以使用位元組的操作是最多的。

如果要java程式實現一個拷貝功能,應該選用位元組流進行操作(可能拷貝的是圖片),並且採用邊讀邊寫的方式(節省記憶體)。

最好在流前面加上緩衝流,為了提高效能和讀取速度

課後練習

  1. 使用字元流讀取一個字元檔案,並把其中的所有小寫字母變成大寫字母,然後寫入到另外一個檔案中。
public static void smallTOBig(File aFile, File bFile) {
        if (aFile.exists()) {
            try (FileReader fReader = new FileReader(aFile); FileWriter fWriter = new FileWriter(bFile);) {

                char[] cbuf = new char[1024];
                int read = -1;
                while ((read = fReader.read(cbuf)) != -1) {
                    String string = new String(cbuf, 0, read);
                    String upperCase = string.toUpperCase();
                    fWriter.write(upperCase);
                }
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        File aFile = new File("test.txt");
        File bFile = new File("a.txt");
        smallTOBig(aFile, bFile);
    }
  1. (使用字元流實現)編寫程式來實現如下功能

    1. 在D盤下建立一個目錄Letter
    2. 在控制檯顯示下列選項:1 檢視請假條 2 撰寫請假條
      1. 如果使用者選擇2,則提示使用者撰寫請假條,並把撰寫的內容儲存位檔案,存入到Letter資料夾下。
        • 格式如下:
        • 請假人:王寶強
        • 請假日期:2016年8月15日
        • 請假原因:向法院起訴馬蓉離婚…..先請假一天等等
      2. 如果使用者選擇1,則在控制檯輸出請假條的內容。
public static void main(String[] args) {
        System.out.println("1 檢視請假條 2 撰寫請假條");
        Scanner sc = new Scanner(System.in);
        int nextInt = sc.nextInt();
        switch (nextInt) {
        case 1:
            readMessage();
            break;
        case 2:
            writeMessage();
            break;
        default:
            break;
        }

    }

    public static void readMessage() {
        try (FileReader frReader = new FileReader(new File("Letter/b.txt"));) {

            int read = -1;
            char[] cbuf = new char[1024];
            while ((read = frReader.read(cbuf)) != -1) {
                String string = new String(cbuf, 0, read);
                System.out.println(string);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    private static void writeMessage() {
        Scanner sc = new Scanner(System.in);
        System.out.println("請假人:");
        String n1 = sc.next();
        System.out.println("請假日期:");
        String n2 = sc.next();
        System.out.println("請假原因:");
        String n3 = sc.next();
        try (FileWriter fWriter = new FileWriter(new File("Letter/b.txt"));) {
            StringBuffer stringBuffer = new StringBuffer();
            fWriter.write("請假人:" + stringBuffer.append(n1));
            fWriter.write("\r\n");
            fWriter.write("請假日期:" + stringBuffer.append(n2));
            fWriter.write("\r\n");
            fWriter.write("請假原因:" + stringBuffer.append(n3));
            fWriter.write("\r\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
  1. 寫一個程式,記錄程式在執行時出現過的錯誤,儲存成錯誤日誌!。如:追加寫入:true

    • 在輸入Int型別的時候輸錯,把這個記錄寫入到檔案中。

    • 在String 型別 轉換 int型別的時候如果出錯,把錯誤的記錄寫入到檔案中。

    • 格式如下:

      err:2017年3月30日 15:26:33 字串轉換Int失敗 不能把 abc 轉成 int 型別。

      err:2017年3月30日 15:27:12 Scanner輸入型別錯誤,要求輸入int,卻輸入了”xyz”。

    提示:捕獲程式有可能出現錯誤的地方,在catch語句中 將錯誤的資訊 用自己的語言組織,寫入到File中。

public static void collectErr(File file) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入你要輸入的整數值");
        try {
            int nextInt = scanner.nextInt();

        } catch (Exception e) {
            try (FileWriter fWriter = new FileWriter(file, true);) {
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
                Date date = new Date();
                String format = simpleDateFormat.format(date);
                fWriter.write(format + "符串轉換Int失敗 不能把輸入的轉成 int 型別" + "\r\n");
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            System.out.println("找到一次錯誤");
        }

    }

    public static void collect2Err(File file) {
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.println("請輸入你要輸入的字元型值");
            String next = scanner.next();
            int parseInt = Integer.parseInt(next);
        } catch (NumberFormatException e) {
            try (FileWriter fWriter = new FileWriter(file, true);) {
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
                Date date = new Date();
                String format = simpleDateFormat.format(date);
                fWriter.write(format + "Scanner輸入型別錯誤,要求輸入int,卻輸入了xyz" + "\r\n");
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            System.out.println("又找到一次錯誤");
        }

    }

    public static void main(String[] args) {
        File file = new File("d.txt");
        collectErr(file);
        collect2Err(file);
    }