1. 程式人生 > >java IO(三):字符流

java IO(三):字符流

bili iso define 自動 繼續 when 註意點 elements ask

字符流按字符個數輸入、輸出數據。

1.Reader類和FileReader類

Reader類是字符輸入流的超類,FileReader類是讀取字符的便捷類,此處的便捷是相對於其父類(另一個字符輸入流)InputStreamReader而言的。

read()每單字符讀取:

import java.io.*;


public class FileR {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("D:/temp/hello.txt"
); int ch = 0; while((ch=fr.read())!=-1) { System.out.println((char)ch); } fr.close(); } }

read(char[] c)讀取字符緩沖到字符數組:

import java.io.*;

public class FileR {
    public static void main(String[] args) throws IOException {
        FileReader fr = new
FileReader("D:/temp/hello.txt"); int len = 0; char[] buf = new char[1024]; while ((len=fr.read(buf))!=-1) { System.out.println(new String(buf,0,len)); //字符數組轉換為字符串輸出 } fr.close(); } }

2.Writer類和FileWriter類

Writer類是字符輸出流的超類,FileWriter類是輸出字符的便捷類,此處的便捷是相對於其父類(另一個字符輸出流)InputStreamWriter而言的。

import java.io.*;

public class FileW {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("d:/temp/hellow.txt");

        fw.write("a你好,謝謝,再見!");
        fw.flush();   //盡量每write一次就flush一次
        fw.close();
    }
}

flush()和close()的註意點:

(1).close()自帶flush(),它在關閉流之前會自動先flush()一次。
(2).flush()後流還能繼續使用,而close()後流就被關閉不可再被使用。
(3).為了防止數據丟失,應盡量每write一次就flush一次。但最後一次可不用flush(),因為close()自帶flush()。

3.InputStreamReader類和OutputStreamWriter類

FileReader和FileWriter分別是InputStreamReader和OutputStreamWriter的便捷類,便捷類的意思是前兩個類可以簡化後兩個類,同時又能達到相同的目的。實際上,FileReader和FileWriter是InputStreamReader和OutputStreamWriter的子類,等價於它們的默認形式。

InputStreamReader、OutputStreamWriter可以看作是字節流和字符流之間的橋梁。前者將字符按照字符集轉換為字節(二進制格式)並讀取字符,這稱為編碼(例如:a->97(01100001));後者將字節(二進制格式)按照字符集轉換為字符並寫入字符,這稱為解碼(例如:97(01100001)->a)

InputStreamReader、OutputStreamWriter的默認字符集采用的是操作系統的字符集,對於簡體中文的Windows系統,默認采用的是GBK字符集。

以下是OutputStreamWriter以utf-8編碼格式寫入字符的示例:

import java.io.*;

public class OutputStreamW {
    public static void main(String[] args) throws IOException {
        WriteCN();
    }

    public static void WriteCN() throws IOException {
        OutputStreamWriter osw = 
                new OutputStreamWriter(new FileOutputStream("d:/temp/hellow.txt"),"utf-8");
              //new OutputStreamWriter(new FileOutputStream("d:/temp/hellow.txt"));//采用默認gbk字符集寫入
        osw.write("a你好,謝謝,再見!!!");
        osw.flush();
        osw.close();
    }
}

以下是InputStreamReader讀取上述文件d:\temp\hellow.txt中字符的示例,因為hellow.txt中的字符編碼為UTF-8,因此讀取時必須也也utf-8讀取。假如以默認的gbk字符集讀取,由於每次讀取2個字節,將會把utf-8字符(中文字符占用3個字節)切分開導致亂碼:

import java.io.*;

public class InputStreamR {
    public static void main(String[] args) throws IOException {
        ReadCN();
    }

    public static void ReadCN() throws IOException {
        InputStreamReader isr = 
                new InputStreamReader(new FileInputStream("d:/temp/hellow.txt"),"utf-8");
        //      new InputStreamReader(new FileInputStream("d:/temp/hellow.txt"));//默認字符集,亂碼

/*         int ch = 0;
        while ((ch=isr.read())!=-1) {
            System.out.println((char)ch);
        } */

        int len = 0;
        char[] buf = new char[1024];
        while ((len=isr.read(buf))!=-1) {
            System.out.println(new String(buf,0,len));
        }
        isr.close();
    }
}

4.字節流、字符流的關系(編碼、解碼、編碼表(字符集))

首先說明編碼、解碼和編碼表(字符集)的關系。

編碼:將字符根據編碼表轉換為二進制的0/1數字。
解碼:將二進制根據編碼表轉換為字符。
編碼表:

  1.ascii碼表:使用一個字節中的7位二進制就能表示字母、數字、英文標點等字符。
  2.iso8859-1碼表:(也即latin-1),使用一個字節中的8位二進制,其內包含了ascii碼表中的字符,此外還擴展了歐洲一些語言的字符。
  3.GB2312:中國自編的編碼表,2個字節,收錄了6700多個漢字。兼容ascii。
   GBK:K代表擴展的意思,是GB2312的擴展,占用2字節,其內包含了GB2312的碼表,收錄了2萬多個漢字。
   GB18030:新的編碼表,變長(可能1、2、4字節),其內包含了GB2312和GBK的碼表,收錄了7萬多個漢字。
  4.Unicode:收錄了全世界幾乎所有的字符,但無論什麽字符都占用2個字節,不足兩個字節的0占位。
  5.UTF-8:解決了unicode的缺點,它使用變長1-4字節來表示一個字符(空間能省則省),其中ascii部分仍占用1個字節(仍兼容ascii)。和Unicode不同的是,UTF-8中的中文字符占用3個字節。

因此,需要知道的是:

  • (1)所有字符集都兼容ascii碼表;
  • (2)一般考慮字符集時,需要考慮的碼表大致分:ascii、latin-1、GBK、utf-8。
  • (3)不同碼表之間,因為編碼、解碼規則不一樣,會導致文本亂碼問題。
  • (4)以上都是文本的編碼、解碼。除了文本,還有媒體類數據,它們都有各自的編碼、解碼規則。實際使用過程中,采用什麽方式編碼、解碼,取決於打開文件的程序,例如不能用記事本類程序打開媒體類(圖片、音頻、視頻等)文件,不能用視頻播放器打開文本文件。

再來說明字符流和字節流的關系,也就是實現字符流的原理。

對於字符串"abcde",使用字節流能夠很輕松地讀取、寫入,但對於字符串"a你好,謝謝,再見!"這樣的中文字符(假設它們是gbk編碼的),無論是采用字節流的單字節讀取還是以字節數組讀取多個字節的方式都很難實現讀取、寫入。例如,一次讀取2個字節,則第一次讀取的兩個字節為a和"你"的前一個字節,a的ascii碼為97,"你"的前一個字節也是一個數值,假如為196,gbk對196也有對應的編碼,假設對應的字符為"浣",於是第一次的兩個字節經過解碼,得到"a浣",而非"a你",這已經亂碼了。而且很多時候,編碼表中有某些數值並沒有對應的字符,這時看到的就是亂七八糟的符號。

如果采用字符流讀取、寫入字符,則會先將其轉換為字節數組,再對字節數組中的字節進行編碼、解碼。也就是說,字符流的底層還是字節流。而且根據不同的編碼表(字符集),轉換為字節存儲到字節數組中時,數值和占用字節數也是不一樣的。

以InputStreamReader和OutputStreamWriter這兩個字符流按照utf-8字符集解析"你好"兩個字符為例。

技術分享圖片

5.InputStreamReader/OutputStreamWriter和FileReader/FileWriter的區別

FileReader/FileWriter類是InputStreamReader/OutputStreamWriter類的子類,它們都能處理字符。區別是前者是後者的便捷類,是後者的默認形式。

也就是說,當InputStreamReader/OutputStreamWriter采用默認字符集時,它們和FileReader/FileWriter是等價的。即以下三條代碼是等價的。

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));   //默認字符集
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");  //指定為默認字符集
FileReader fr = new FileReader("a.txt");   //使用便捷類FileReader

所以,如果采用的是默認字符集,最佳方式是采用FileReader/FileWriter,但如果需要指定字符集,則必須使用InputStreamReader/OutputStreamWriter。

6.字符流復制文本類文件

import java.io.*;

public class CopyFileByChar {
    public static void main(String[] args) throws IOException {
        copy("d:/temp/big.log","d:/temp/big_bak.log");
    }

    public static void copy(String src,String dest) throws IOException {
        //字符集必須設置正確
        InputStreamReader isr = new InputStreamReader(new FileInputStream(src),"gbk");
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(dest),"gbk");

        int len = 0;
        char[] buf = new char[1024];
        while((len=isr.read(buf))!=-1) {
            osw.write(buf,0,len);
            osw.flush();
        }

        osw.close();
        isr.close();
    }
}

7.操作行:BufferedReader類BufferedWriter類

它們是緩沖類字符流,分別用於創建帶有輸入、輸出緩沖區的輸入、輸出字符流。

其實和字符數組的作用差不多,只不過設計為緩沖類後可以使用類的一些方法,最常用的是操作行的方法:

  • BufferedReader的readLine()方法:讀取一行數據。只要遇到換行符(\n)、回車符(\r)都認為一行結束。當讀取到流末尾時返回null。
  • BufferedWriter的newLine()方法:寫入一個換行符。

例如,下面是用這兩個類來復制文本文件d:\temp\big.log。

import java.io.*;


public class CopyByBuffer {
    public static void main(String[] args) throws IOException {
        copy("d:/temp/big.log","d:/temp/big_bak.log");
    }

    public static void copy(String src,String dest) throws IOException {

        // src buffer & dest buffer
        InputStreamReader isr = new InputStreamReader(new FileInputStream(src),"gbk");
        BufferedReader bufr = new BufferedReader(isr);

        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(dest),"gbk");
        BufferedWriter bufw = new BufferedWriter(osw);

        String line = null;
        while((line=bufr.readLine())!=null) {
            bufw.write(line);
            bufw.newLine();  
            bufw.flush();
        }

        bufw.close();
        bufr.close();
    }
}

對於大文件來說,這BufferedReader類操作數據其實比字符數組的速度要慢,因為每次緩沖一行,一行才不到1k而已(除非行很長),而char[]通常都設置為好幾k,一次就能讀很多行。

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!

java IO(三):字符流