IO流(操作文件內(nèi)容): 字符流

概述

計算機并不區(qū)分二進制文件與文本文件。所有的文件都是以二進制形式來存儲的,因此,從本質(zhì)上說,所有的文件都是二進制文件。所以字符流是建立在字節(jié)流之上的,它能夠提供字符層次的編碼和解碼。例如: 在寫入一個字符時, Java虛擬機會將字符轉(zhuǎn)為文件指定的編碼(默認(rèn)是系統(tǒng)默認(rèn)編碼), 在讀取字符時, 再將文件指定的編碼轉(zhuǎn)化為字符

  • 常見的碼表如下:
    • ASCII : 美國標(biāo)準(zhǔn)信息交換碼。用一個字節(jié)的7位可以表示
    • ISO8859-1 : 拉丁碼表(歐洲碼表)用一個字節(jié)的8位表示。又稱Latin-1(拉丁編碼)或“西歐語言”。ASCII碼是包含的僅僅是英文字母,并且沒有完全占滿256個編碼位置,所以它以ASCII為基礎(chǔ),在空置的0xA0-0xFF的范圍內(nèi),加入192個字母及符號,藉以供使用變音符號的拉丁字母語言使用。從而支持德文,法文等。因而它依然是一個單字節(jié)編碼,只是比ASCII更全面
    • GB2312 : 英文占一個字節(jié),中文占兩個字節(jié).中國的中文編碼表
    • GBK : 中國的中文編碼表升級,融合了更多的中文文字符號
    • Unicode : 國際標(biāo)準(zhǔn)碼規(guī)范,融合了多種文字。所有文字都用兩個字節(jié)來表示,Java語言使用的就是unicode
    • UTF-8 : 最多用三個字節(jié)來表示一個字符
      (我們以后接觸最多的是iso8859-1、gbk、utf-8)
      查看上述碼表后,很顯然中文的‘中’在iso8859-1中是沒有對映的編碼的。或者一個字符在2中碼表中對應(yīng)的編碼不同,例如有一些字在不同的編碼中是有交集的,例如bjg5 和gbk 中的漢字簡體和繁體可能是一樣的,就是有交集,但是在各自碼表中的數(shù)字不一樣
  • 例 : 使用gbk 將中文保存在計算機中,中國 對映 100 200, 如果使用big5 打開可能 ? ..., 不同的編碼對映的是不一樣的。很顯然,我們使用什么樣的編碼寫數(shù)據(jù),就需要使用什么樣的編碼來對數(shù)據(jù)。
    ISO8859-1:一個字節(jié)
    GBK: 兩個字節(jié)包含了英文字符和擴展的中文(ISO8859-1+中文字符)
    UTF-8 : 萬國碼,推行的。是1~3個字節(jié)不等長。英文存的是1個字節(jié),中文存的是3個字節(jié),是為了節(jié)省空間
  • 將指定位置的文件通過字節(jié)流讀取到控制臺
public class TestIo {
    public static void main(String[] args) throws IOException {
        String path = "c:\\a.txt";
        writFileTest();
        readFileByInputStream(path);
    }

    private static void readFileByInputStream(String path) throws IOException {
        FileInputStream fis = new FileInputStream(path);

        int len = 0;
        while ((len = fis.read()) != -1) {
            System.out.print((char) len);
        }
    }

    private static void writFileTest() throws FileNotFoundException,
            IOException {
        // 創(chuàng)建文件對象
        File file = new File("c:\\a.txt");
        // 創(chuàng)建文件輸出流
        FileOutputStream fos = new FileOutputStream(file);
        fos.write("中國".getBytes());
        fos.close();
    }
}

發(fā)現(xiàn)控制臺輸出的信息:???ú 是這樣的東西,打開a.txt 文本發(fā)現(xiàn)漢字 中國確實寫入成功。那么說明使用字節(jié)流處理中文有問題。仔細(xì)分析,我們的FileInputStream輸入流的read() 一次是讀一個字節(jié)的,返回的是一個int顯然進行了自動類型提升。那么我們來驗證一下“中國”對應(yīng)的字節(jié)是什么 ? 使用:"中國".getBytes() 即可得到字符串對應(yīng)的字節(jié)數(shù)組。是[-42, -48, -71, -6]同樣,將read方法返回值直接強轉(zhuǎn)為byte ,發(fā)現(xiàn)結(jié)果也是-42, -48, -71, -6 。代碼如下:

public class TestIo {
    public static void main(String[] args) throws IOException {
        String path = "c:\\a.txt";
        writFileTest();
        readFileByInputStream(path);
        //查看中國對應(yīng)的編碼
        System.out.println(Arrays.toString("中國".getBytes()));
    }

    private static void readFileByInputStream(String path) throws IOException{
        FileInputStream fis = new FileInputStream(path);
        int len = 0;
        while ((len = fis.read()) != -1) {
            System.out.println((byte)len);
        }
    }

    private static void writFileTest() throws FileNotFoundException,
            IOException {
        // 創(chuàng)建文件對象
        File file = new File("c:\\a.txt");
        // 創(chuàng)建文件輸出流
        FileOutputStream fos = new FileOutputStream(file);
        fos.write("中國\r\n".getBytes());
        fos.close();
    }
}

那么中國對應(yīng)的是-42, -48, -71, -6是4個字節(jié)。 那就是一個中文占2個字節(jié),(這個和編碼是有關(guān)系的)很顯然,我們的中文就不能夠再一個字節(jié)一個字節(jié)的讀了。所以字節(jié)流處理字符信息時并不方便那么就出現(xiàn)了字符流。字節(jié)流是字符流是以字符為單位。

  • 體驗字符流:
public static void main(String[] args) throws IOException {
        
        String path = "c:\\a.txt";
        readFileByReader(path);
    }
private static void readFileByReader(String path) throws IOException {
        FileReader fr = new FileReader(path);
        int len = 0;
        while ((len = fr.read()) != -1) {
            System.out.print((char) len);
        }
    }
  • 總結(jié):字符流 = 字節(jié)流 + 編碼表,為了更便于操作文字?jǐn)?shù)據(jù)。字符流的抽象基類:Reader , Writer。由這些類派生出來的子類名稱都是以其父類名作為子類名的后綴,如FileReader、FileWriter。

Reader輸入字符流

  • Reader中常見的方法
    1. int read() : 讀取一個字符. 返回的是讀到的那個字符. 如果讀到流的末尾, 返回-1
    2. int read(char[]) : 將讀到的字符存入指定的數(shù)組中, 返回的是讀到的字符個數(shù), 也就是往數(shù)組里裝的元素的個數(shù). 如果讀到流的末尾, 返回-1
    3. close() : 讀取字符其實用的是window系統(tǒng)的功能, 就希望使用完畢后, 進行資源的釋放, 由于Reader也是抽象類, 所以想要使用字符輸入流需要使用Reader的實現(xiàn)類(FileReader)
  • FileReader
    1. 用于讀取文本文件的流對象
    2. 用于關(guān)聯(lián)文本文件
    3. FileReader構(gòu)造函數(shù) : 在讀取流對象初始化的時候, 必須要指定一個被讀取的文件, 如果該文件不存在會發(fā)生FileNotFoundException
public class IOTest_Reader {
    public static void main(String[] args) throws Exception {
        String path = "c:/a.txt";
        // readFileByInputStream(path);
        readFileByReader(path);
    }
    /**
     * 使用字節(jié)流讀取文件內(nèi)容
     * 
     * @param path
     */
    public static void readFileByInputStream(String path) throws Exception {
        InputStream in = new FileInputStream(path);

        int len = 0;
        while ((len = in.read()) != -1) {
            System.out.print((char) len);
        }

        in.close();
    }

    /**
     * 使用字符流讀取文件內(nèi)容
     */
    public static void readFileByReader(String path) throws Exception {
        Reader reader = new FileReader(path);
        int len = 0;
        while ((len = reader.read()) != -1) {
            System.out.print((char) len);
        }
        reader.close();
    }
}

Writer輸入字符流

  • Writer中的常見的方法:
    1. write(ch) : 將一個字符寫入到流中。
    2. write(char[]) : 將一個字符數(shù)組寫入到流中。
    3. write(String) : 將一個字符串寫入到流中。
    4. flush() : 刷新流,將流中的數(shù)據(jù)刷新到目的地中,流還存在。
    5. close() : 關(guān)閉資源, 在關(guān)閉前會先調(diào)用flush(),刷新流中的數(shù)據(jù)去目的地。然流關(guān)閉
  • 發(fā)現(xiàn)基本方法和OutputStream 類似,有write方法,功能更多一些。可以接收字符串。同樣道理Writer是抽象類無法創(chuàng)建對象。查閱API文檔,找到了Writer的子類FileWriter
  • FileWriter
    1. 將文本數(shù)據(jù)存儲到一個文件中
public class IoTest2_Writer {

    public static void main(String[] args) throws Exception {
        String path = "c:/ab.txt";

        writeToFile(path);
    }

    /**
     * 寫指定數(shù)據(jù)到指定文件中
     * 
     */
    public static void writeToFile(String path) throws Exception {
        Writer writer = new FileWriter(path);
        writer.write('中');
        writer.write("世界".toCharArray());
        writer.write("中國");

        writer.close();
    }
}
  1. 追加文件 : 默認(rèn)的FileWriter方法新值會覆蓋舊值,想要實現(xiàn)追加功能需要
    使用如下構(gòu)造函數(shù)創(chuàng)建輸出流 append值為true即可。
- `` FileWriter(String fileName, boolean append) ``  
- `` FileWriter(File file, boolean append) ``
  1. flush方法 : 如果使用字符輸出流,沒有調(diào)用close方法,會發(fā)生什么?
private static void writeFileByWriter(File file) throws IOException {
        FileWriter fw = new FileWriter(file);
        fw.write('新');
fw.flush();
        fw.write("中國".toCharArray());
        fw.write("世界你好!!!".toCharArray());
        fw.write("明天"); 
        // 關(guān)閉流資源
        //fw.close();
    }
      程序執(zhí)行完畢打開文件, 發(fā)現(xiàn)沒有內(nèi)容寫入. 原來需要使用flush方法. 刷新該流的緩沖, 為什么只要指定close方法就不用再flush方法? 因為close也調(diào)用了flush方法.

字符流拷貝文件

一個文本文件中有中文有英文字母,有數(shù)字。想要把這個文件拷貝到別的目錄中。我們可以使用字節(jié)流進行拷貝,使用字符流呢?肯定也是可以的

  • 字符流拷貝文件實現(xiàn)
    1. 一次讀一個字符就寫一個字符,效率不高。把讀到的字符放到字符數(shù)組中,再一次性的寫出。
public static void main(String[] args) throws Exception {
        String path1 = "c:/a.txt";
        String path2 = "c:/b.txt";

        copyFile(path1, path2);
    }

    /**
       * 使用字符流拷貝文件
       */
    public static void copyFile(String path1, String path2) throws Exception {
        Reader reader = new FileReader(path1);
        Writer writer = new FileWriter(path2);

        int ch = -1;
        while ((ch = reader.read()) != -1) {
            writer.write(ch);
        }

        reader.close();
        writer.close();
}
  1. 字節(jié)流可以拷貝視頻和音頻等文件,那么字符流可以拷貝這些嗎?經(jīng)過驗證拷貝圖片是不行的。發(fā)現(xiàn)丟失了信息,為什么呢?計算機中的所有信息都是以二進制形式進行的存儲(1010)圖片中的也都是二進制, 在讀取文件的時候字符流自動對這些二進制按照碼表進行了編碼處理,但是圖片本來就是二進制文件,不需要進行編碼。有一些巧合在碼表中有對應(yīng),就可以處理,并不是所有的二進制都可以找到對應(yīng)的。信息就會丟失。所以字符流只能拷貝以字符為單位的文本文件 (以ASCII碼為例是127個, 并不是所有的二進制都可以找到對應(yīng)的ASCII, 有些對不上的, 就會丟失信息)
public static void main(String[] args) throws Exception {
    String path1 = "c:/a.txt";
    String path2 = "c:/b.txt";
    copyFile(path1, path2);
}

public static void copyFile3(String path1, String path2) throws Exception {
        Reader reader = new FileReader(path1);
        Writer writer = new FileWriter(path2);

        int ch = -1;
        char [] arr=new char[1024];
        while ((ch = reader.read(arr)) != -1) {
            writer.write(arr,0,ch);
        }
        reader.close();
        writer.close();
}

字符流的異常處理

public static void main(String[] args) throws Exception {
        String path1 = "c:/a.txt";
        String path2 = "c:/b.txt";

        copyFile2(path1, path2);
    }

/**
     * 使用字符流拷貝文件,有完善的異常處理
     */
    public static void copyFile2(String path1, String path2) {
        Reader reader = null;
        Writer writer = null;
        try {
            // 打開流
            reader = new FileReader(path1);
            writer = new FileWriter(path2);

            // 進行拷貝
            int ch = -1;
            while ((ch = reader.read()) != -1) {
                writer.write(ch);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 關(guān)閉流,注意一定要能執(zhí)行到close()方法,所以都要放到finally代碼塊中
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                try {
                    if (writer != null) {
                        writer.close();
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
```

####字符流的緩沖區(qū)
- 查看Reader發(fā)現(xiàn)Reader, 操作的是字符, 我們就不需要進行編碼解碼操作, 由字符流讀到二進制, 自動進行解碼得到字符, 寫入字符自動編碼成二進制. Reader有一個子類BufferedReader. 子類繼承父類顯然子類可以重寫父類的方法, 也可以增加自己的新方法. 例如一次讀一行就是常用的操作.那么BufferedReader 類就提供了這個方法, 可以查看readLine()方法具備 一次讀取一個文本行的功能. 很顯然, 該子類可以對功能進行增強
- 體驗BufferedReader
```java
public class IoTest_BufferedReader {
    public static void main(String[] args) throws IOException {
        readFile("c:\\a.txt");
    }

    private static void readFile(String path) throws IOException {
        Reader read = new FileReader(path);

        BufferedReader br = new BufferedReader(read);
        
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }

    }
}
```
- 注意:在使用緩沖區(qū)對象時,要明確,緩沖的存在是為了增強流的功能而存在,所以在建立緩沖區(qū)對象時,要先有流對象存在.緩沖區(qū)的出現(xiàn)提高了對流的操作效率。原理:其實就是將數(shù)組進行封裝
- 使用字符流緩沖區(qū)拷貝文本文件.
```java
public class Demo {
    public static void main(String[] args) throws IOException {
        // 關(guān)聯(lián)源文件
        File srcFile = new File("c:\\linux大綱.txt");
        // 關(guān)聯(lián)目標(biāo)文件
        File destFile = new File("d:\\linux大綱.txt");
        // 實現(xiàn)拷貝
        copyFile(srcFile, destFile);

    }

    private static void copyFile(File srcFile, File destFile)
            throws IOException {
        // 創(chuàng)建字符輸入流
        FileReader fr = new FileReader(srcFile);
        // 創(chuàng)建字符輸出流
        FileWriter fw = new FileWriter(destFile);

        // 字符輸入流的緩沖流
        BufferedReader br = new BufferedReader(fr);
        // 字符輸出流的緩沖流
        BufferedWriter bw = new BufferedWriter(fw);

        String line = null;
        // 一次讀取一行
        while ((line = br.readLine()) != null) {
            // 一次寫出一行.
            bw.write(line);
            // 刷新緩沖
            bw.flush();
            // 進行換行,由于readLine方法默認(rèn)沒有換行.需要手動換行
            bw.newLine();
        }
        // 關(guān)閉流
        br.close();
        bw.close();
    }
}
```
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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