Java 基礎(九)字符流

終於結束了集合的學習,今天咱們就開始學習 I/O的操做了。 I/O 系列的內容分爲 I/O概述、字符流、字節流。今天要學的是 I/O和字符流的操做設計模式

因爲概述篇幅較短,因此就把概述壓縮到這裏來了。bash

I/O 概述

I/O:即 Input Output,輸入輸出的意思。app

  • IO 流用來處理設備之間的數據傳輸。
  • JAVA 對數據的操做都是經過流的方式
  • JAVA 用於操做流的對象都在 IO 包裏面
  • 流的操做分兩種:字符流、字節流
  • 流的流向分兩種:輸入流、輸出流

對數據的操做,其實就是對 File 文件。我偷了一張祖師爺傳下來的圖來描述 IO 流類結構關係。函數

從圖中能夠看出,都是從這如下四個類中派生出來的子類,子類的類型也好區分,後綴都是抽象基類名。性能

  • 字節流抽象基類
    • InputStream
    • OutputStream
  • 字符流抽象基類
    • Reader
    • Writer

IOException

IO 異常大體分爲三種,一是 IO 異常、二是找不到文件異常、三是沒有對象異常。學習

所以,咱們在異常處理的時候,比較嚴峻的寫法應該這樣ui

FileWriter fileWriter = null;
try {
    fileWriter = new FileWriter("demo.txt");
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} finally {
    try {
        if (fileWriter != null) {
            fileWriter.close();
            }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}複製代碼

字符流-Reader/Writer

Reader 和 Writer的定義

Reader: 用於讀取字符流的抽象類。子類必須實現的方法只有 read(char[], int, int) 和 close()。可是,多數子類將重寫此處定義的一些方法,以提供更高的效率和/或其餘功能。this

Writer: 寫入字符流的抽象類。子類必須實現的方法僅有 write(char[], int, int)、flush() 和 close()。可是,多數子類將重寫此處定義的一些方法,以提供更高的效率和/或其餘功能。編碼

字符流的讀寫FileReader 和 FileWriter。

先看看基本運用吧~spa

try {
        // 建立讀取流和寫入流
        FileReader fileReader = new FileReader("raw.txt");
        FileWriter fileWriter = new FileWriter("target.txt");
        // 讀取單個字符,自動往下讀
        int ch;
        while ((ch = fileReader.read()) != -1) {
            fileWriter.write(ch);
        }
            fileReader.close();
            fileWriter.close();
    } catch (IOException e) {
        e.printStackTrace();
    }複製代碼

每次讀取一個字符再執行寫入操做,效率比較慢,咱們能夠嘗試一次讀取更多的數據再一次性寫入。

try {
        // 建立讀取流和寫入流
        FileReader fileReader = new FileReader("raw.txt");
        FileWriter fileWriter = new FileWriter("target.txt");
        // 讀取1024個字符,自動往下讀
        char[] buf = new char[1024];
        while (fileReader.read(buf) != -1) {
            fileWriter.write(buf);
        }
        fileReader.close();
        fileWriter.close();
    } catch (IOException e) {
        e.printStackTrace();
    }複製代碼

以上兩種方法實現了文件的讀寫操做,將 raw.txt 讀取寫入 targer.txt 文件裏面。

同時,這步操做也能夠當成是文件的拷貝

須要注意的是:

  • 讀取文件時,若是路徑對應的文件不存在會報 IOException
  • 寫入文件時,若是路徑對應的文件不存在會自動在該目錄下建立文件

若是要實現追加寫入文件的操做,好比將 raw1.txt 和 raw2.txt 共同寫入 target.txt 裏面,只須要在建立 FileWriter 的時候使用兩個參數的構造方法便可,如:FileWriter fileWriter = new FileWriter("target.txt",true);

至於源碼讀寫的實現,我簡單說一下吧。其實 FileReader/FileWriter 的構造方法裏面都有 new FileInputStream/FileOutputStream 的對象,而後繼承的是InputStreamReader/OutputStreamWriter.這說明啥,我想你們內心應該有數了吧,其實仍是調用了字節流的讀取,而後使用StreamEncoder/StreamDecoder進行編碼解碼操做。

具體流程是這樣的,這裏每次讀/寫太長了,我只說讀取操做了,反正都是相對的。

  1. 首先建立 FileReader 對象,FileReader對象構造方法裏面建立了FileInputStream。而後再使用 FileInputStream 做爲參數,建立 StreamDecoder對象。
  2. 調用FileReader.read()讀取一個字符,實際上就是調用了 StreamDecoder 同時讀取兩個字節,再使用這兩個字節組成一個字符返回。

字符流的緩衝區

字符流的緩衝區,提升了對數據的讀寫效率,他有兩個子類

  • BufferedWriter
  • BufferedReader

緩衝區要結合流纔可使用
在流的基礎上對流的功能進行了加強

源碼就不帶着你們一塊兒讀了,我給你們分析一下 BufferedWriter 的思想。如下內容劃重點,期末考試要考!

要想理解 BufferReader,就要先理解它的思想。BufferReader 的做用是爲其它Reader 提供緩衝功能。建立BufferReader 時,咱們會經過它的構造函數指定某個Reader 爲參數。BufferReader 會將該Reader 中的數據分批讀取,每次讀取一部分到緩衝中;操做完緩衝中的這部分數據以後,再從Reader 中讀取下一部分的數據。
爲何須要緩衝呢?緣由很簡單,效率問題!緩衝中的數據其實是保存在內存中,而原始數據多是保存在硬盤中;而咱們知道,從內存中讀取數據的速度比從硬盤讀取數據的速度至少快10倍以上。
那幹嗎不乾脆一次性將所有數據都讀取到緩衝中呢?第一,讀取所有的數據所須要的時間可能會很長。第二,內存價格很貴,容量不想硬盤那麼大。

經過字符流緩衝區來複制文件操做

還用上面那個案例

BufferedReader bufferedReader;
BufferedWriter bufferedWriter;
try {
    // 建立讀取流和寫入流
    bufferedReader = new BufferedReader(new FileReader("raw.txt"));
    bufferedWriter = new BufferedWriter(new FileWriter("target.txt", true));
    // 讀取一行字符串
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        bufferedWriter.write(line);
    }
    bufferedReader.close();
    bufferedWriter.close();
} catch (IOException e) {
    e.printStackTrace();
}複製代碼

沒什麼特別的,很簡單

仿寫一個 readLine

上文中,出現了一個 readLine 方法,能夠一次讀取一行字符串。
其實這個一次讀取一行字符串仍是蠻有用的,好比說讀取一些Key-value 形式的配置文件。

咱們來看看 JDK 中關於 readLine 的描述

讀取一個文本行。經過下列字符之一便可認爲某行已終止:換行 ('\n')、回車 ('\r') 或回車後直接跟着換行。
返回:包含該行內容的字符串,不包含任何行終止符,若是已到達流末尾,則返回 null
拋出:IOException - 若是發生 I/O 錯誤

因此,咱們本身要封裝一個 readLine,只須要判斷讀取到的字符是否爲'\n'、'\r',再一次性返回就好了。

class MyBufferReaderLine {

    private Reader fr;

    public MyBufferReaderLine(Reader fr) {
        this.fr = fr;
    }

    // 一次讀取一行的方法
    public String readLine() throws IOException {

        // 定義臨時容器
        StringBuilder sb = new StringBuilder();
        int ch = 0;
        while ((ch = fr.read()) != -1) {

            if (ch == '\r' || ch == '\n') {
                return sb.toString();
            } else {
                sb.append((char) ch);
            }
        }
        if(sb.length() != 0){
            return sb.toString();
        }
        return null;
    }

    public void close() throws IOException {
        fr.close();
    }
}複製代碼

代碼實現很簡單,就是參考 BufferedReader 寫的一個包裝類。

LineNumberReader

大家先感覺一下這個類的用法

FileReader fr;
try {
    fr = new FileReader("test.txt");
    LineNumberReader lnr = new LineNumberReader(fr);
    String line;
    while ((line = lnr.readLine()) != null) {
        System.out.println(lnr.getLineNumber() + ":" + line);
    }
    lnr.close();
} catch (IOException e) {
    e.printStackTrace();
}複製代碼

這貨和咱們剛剛手寫的MyBufferReaderLine 基本沒啥區別,繼承自BufferedReader ,而後多了一個lineNumber 屬性,lineNumber用來記錄當前行數。
實現沒有什麼意義,咱們在MyBufferReaderLine 上添加一個字段lineNumber,每次 readLine 成功以後 lineNumber++ 便可。

可是,爲何要講他呢,由於他和 BufferedReader 同樣,也是一個包裝類啊。

包裝類就是裝飾設計模式啊~

好了,大家都知道了,就是提一下裝飾模式。

裝飾模式

當想要對已有的對象進行功能加強時,能夠定義一個類,將已有對象傳入,而且提供增強功能,那麼自定義的該類就稱爲裝飾類。
裝飾模式又名包裝(Wrapper)模式。裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關係的一個替代方案。

可能有的同窗會問,那麼爲何不用繼承呢?我寫一個新的類,繼承、再添加擴展方法或者重寫方法也能夠實現呀。

假如咖啡廳賣咖啡,運營可一段時間,發現客戶對咖啡的甜度有不一樣的需求,有以下三種需求加少許糖、通常糖、多糖。代碼實現能夠給 Coffee 添加一個糖量的屬性,可是一開始設計 Coffee 這個類的時候沒有加這個屬性,根據開發守則,咱們是不該該去修改原 Coffee 類,此時能夠選擇新增三個子類,LowSurgeCoffee、MidSurgeCoffee、HightSurgeCoffee,或者使用裝飾模式,添加3個不一樣糖量的 SurgeDecorator。此時,使用裝飾模式和繼承沒什麼區別。可是運行了一段時間以後,需求又加了,咖啡須要新增口味卡布奇諾和摩卡。此時再組合以前的三種糖量,一共須要9個咖啡類。可是若是使用裝飾模式,只須要新增摩卡和卡布奇諾裝飾器就好了。一共6個裝飾類。以後再擴展新的口味須要的子類是乘算,可是若是是裝飾類,就只是加算。

以上這個例子沒有代碼實現,由於我懶。。。。。。

針對的問題:

動態地給一個對象添加一些額外的職責。就增長功能來講,Decorator模式相比生成子類更爲靈活。不改變接口的前提下,加強所考慮的類的性能。

什麼時候使用
  • 須要擴展一個類的功能,或給一個類增長附加責任。
  • 須要動態的給一個對象增長功能,這些功能能夠再動態地撤銷。
  • 須要增長一些基本功能的排列組合而產生的很是大量的功能,從而使繼承變得不現實。
優缺點
  • 裝飾者模式比繼承要靈活,避免了繼承體系的臃腫,並且下降了類與類之間的關係
  • 裝飾類由於加強已有對象,具有功能和已有的想相同,只不過提供了更強的功能,因此裝飾類和被裝飾類一般屬於一個體系中的
相關文章
相關標籤/搜索