日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【Java核心技术卷】I/O详析

發布時間:2023/12/9 java 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java核心技术卷】I/O详析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 概述
  • Java io基本概念
    • 關于流
    • 流的分類
  • Java io框架
    • 一、以字節為單位的輸出流的框架圖
      • (1)框架圖圖示
      • (2)OutputStream詳解
      • (3)OutputStream子類
      • (4)引申:打印流
    • 二、以字節為單位的輸入流的框架圖
      • (1)框架圖圖示
      • (2)InputStream詳解
      • (3)InputStream子類
      • (4)引申:緩沖流(含字節輸出流的內容)
      • (5)引申:數據流(含字節輸出流的內容)
    • 三、以字符為單位的輸入流和輸出流的框架圖
      • (1)框架圖圖示
      • (2)框架詳解
      • (3)引申:過濾器流
  • Java io中的設計模式
    • 適配器模式
    • 裝飾者模式
  • 復盤

概述

java的IO系統的設計是為了實現 “文件控制臺網絡設備” 這些IO設置的通信,它建立在流之上,輸入流讀取數據,輸出流寫出數據,不同的流類,會讀/寫某個特定的數據源。

數據源(Data Source)顧名思義,數據的來源

在展開整個面之前有必要弄清楚三個概念: 比特、字節和字符

  • Bit最小的二進制單位 ,是計算機的操作部分,取值0或者1,也是計算機網絡的物理層最小的傳輸單位。
  • Byte是計算機操作數據的最小單位由8位bit組成 取值(-128-127)
  • Char是用戶的可讀寫的最小單位,在Java里面由16位bit組成 取值(0-65535)

Java將IO進行了封裝,所以我們看到的都是API,我們能做到的就是理解熟悉這些API,靈活運用即可。如果想要深入理解IO,建議從C語言入手,因為C語言是唯一一個能將IO講明白的高級語言。

Java的IO系統是Java SE學習的重要一環,Java的文件操作 , Java網絡編程基于此。它對你理解Tomcat的架構等也是必備的。


Java io基本概念

關于流

流是一串連續不斷的數據的集合,你可以把它理解為水管里的水流,在水管的源頭有水的供應,在另一端則是娟娟的水流,數據寫入程序可以理解為供水,數據段會按先后順序形成一個長的數據流。

對數據讀取程序來說,不知道流在寫入時的分段情況,每次可以讀取其中的任意長度的數據,但只能先讀取前面的數據后,再讀取后面的數據。

流的分類

Java IO中包含字節流、字符流。按照流向還分為輸入流,輸出流

  • 字節流:操作byte類型數據,主要操作類是OutputStream、InputStream的子類;不用緩沖區,直接對文件本身操作。
  • 字符流:操作字符類型數據,主要操作類是Reader、Writer的子類;使用緩沖區緩沖字符,不關閉流就不會輸出任何內容。

輸入流和輸出流的區分很簡單:輸入流是把數據寫入存儲介質的。輸出流是從存儲介質中把數據讀取出來

Java io框架

一、以字節為單位的輸出流的框架圖

(1)框架圖圖示

圖示基于Jdk1.8

(2)OutputStream詳解

OutputStream是抽象類,它是所有字節輸出流的類的超類,它與InputStream構成了IO類層次結構的基礎。

它展示了五種方法,子類針對這五個方法進行拓展, 不管是哪種介質,大都使用同樣的這5種方法

?
值得注意的是 wirte(int b)
它雖然接收0~255的整數,但是實際上會寫出一個無符號字節,因為Java是沒有無符號字節整型數據的,所以這里用int來代替。


?
下面來看這兩個方法,可用于多個字節的處理,相比上面一次處理一個字節效率要更高。

void wirte(byte[] b) void write(byte[] b ,int off ,int len)

參數

  • b:數據讀入的數組
  • off 第一個讀入的字節 應該被放置的位置在b的偏移量
  • len 讀入字節的最大數量

這個還是比較好理解的,就不多說了。


?
當結束一個流的操作的時候 會調用close方法將其關閉,釋放與這個流相關的所有資源,如文件句柄或端口。

文件句柄
在文件I/O中,要從一個文件讀取數據,應用程序首先要調用操作系統函數并傳送文件名,并選一個到該文件的路徑來打開文件。該函數取回一個順序號,即文件句柄(file handle),該文件句柄對于打開的文件是唯一的識別依據。要從文件中讀取一塊數據,應用程序需要調用函數ReadFile,并將文件句柄在內存中的地址和要拷貝的字節數傳送給操作系統。當完成任務后,再通過調用系統函數來關閉該文件。

其實關于結束一個流,在Java6及之前有一個很經典的 完成清理的釋放模式

OutputStream out = null;try {out = new FileOutputStream("/temp/data.txt");}catch (IOException ex){System.err.println(ex.getMessage());}finally {if(out != null){try {out.close();}catch (IOException ex){//忽略}}}

這個不僅可以用于流,還可以用于socket,通道,JDBC的連接

在Java7之后出現一個語法糖 — 帶資源的 try構造。寫法類似于這種:

try(OutputStream out = new FileOutputStream("/temp/data.txt")){//處理輸出流}catch (IOException ex){System.err.println(ex.getMessage());}

由于這個很重要,我們深究一下:

AutoCloseable接口對JDK7新添加的帶資源的try語句提供了支持,這種try語句可以自動執行資源關閉過程。

只有實現了AutoCloseable接口的類的對象才可以由帶資源的try語句進行管理。AutoCloseable接口只定義了close()方法:


Closeable接口也定義了close()方法。實現了Closeable接口的類的對象可以被關閉。

但是注意!!! 從JDK7開始,Closeable擴展了AutoCloseable。因此,在JDK7中,所有實現了Closeable接口的類也都實現了AutoCloseable接口。

關于帶資源的try語句的3個關鍵點:

  • 由帶資源的try語句管理的資源必須是實現了AutoCloseable接口的類的對象。
  • 在try代碼中聲明的資源被隱式聲明為final。
  • 通過使用分號分隔每個聲明可以管理多個資源。
  • 此外請記住,所聲明資源的作用域被限制在帶資源的try語句中。


    我們看一下Java編譯器為我們解析語法糖tryWithResource 做了哪些事情:

    原本的代碼

    public class TryWith {public static void main(String[] args) {try (BufferedReader br = new BufferedReader(new FileReader("c:\\Test.jad"))) {String line;while ((line = br.readLine()) != null) {System.out.println(line);}} catch (IOException e) {e.printStackTrace();}} }

    反編譯之后

    public class TryWith {public TryWith(){}public static void main(String args[]){try{BufferedReader br = new BufferedReader(new FileReader("c:Test.jad"));String line;try{while((line = br.readLine()) != null) System.out.println(line);}catch(Throwable throwable){try{br.close();}catch(Throwable throwable1){throwable.addSuppressed(throwable1);}throw throwable;}br.close();}catch(IOException e){e.printStackTrace();}} }

    ?
    最后一個方法是flush()
    意思是沖刷輸出流,也就是將所有緩沖的數據發送到目的地。這個在 子類 緩沖流BufferedOutputStream 中體現最為明顯


    (3)OutputStream子類

    關于子類的實現這里更多地知識點一下,如果一一詳細介紹,這篇博文實在太長了。(所以把重要的放在下一篇寫)

    • ByteArrayOutputStream 是字節數組輸出流。寫入ByteArrayOutputStream的數據被寫入一個 byte 數組。緩沖區會隨著數據的不斷寫入而自動增長。可使用 toByteArray() 和 toString() 獲取數據。
    • PipedOutputStream 是管道輸出流,它和PipedInputStream一起使用,能實現多線程間的管道通信。
    • FilterOutputStream 是過濾輸出流。它是DataOutputStream,BufferedOutputStream和PrintStream等的超類。
    • DataOutputStream 是數據輸出流。它是用來裝飾其它輸出流,它“允許應用程序以與機器無關方式向底層寫入基本 Java 數據類型”。(簡單來說就是以二進制格式寫出所有的基本Java類型)
    • BufferedOutputStream 是緩沖輸出流。它的作用是為另一個輸出流添加緩沖功能。
    • PrintStream 是打印輸出流。它是用來裝飾其它輸出流,能為其他輸出流添加了功能,使它們能夠方便地打印各種數據值表示形式。
    • FileOutputStream 是文件輸出流。它通常用于向文件進行寫入操作。
    • ObjectOutputStream 是對象輸出流。它和ObjectInputStream一起,用來提供對“基本數據或對象”的持久存儲。

    (4)引申:打印流

    平時我們在控制臺打印輸出,是調用 print 方法和 println 方法完成的,這兩個方法都來自于java.io.PrintStream 類,該類能夠方便地打印各種數據類型的值,是一種便捷的輸出方式。

    打印流PrintStream 為其他輸出流添加了功能,使它們能夠方便地打印各種數據值表示形式。

    PrintStream特點:

  • 只負責數據的輸出,不負責數據的讀取
  • 與其他輸出流不同,Printstream 永遠不會拋出IOException
  • 有特有的方法,print,println void print(任意類型的值)void printin(任意類型的值并換行)構造方法:
  • API信息

    Print Stream(File file):輸出的目的地是一個文件PrintStream(Outputstream out):輸出的目的地是一個字節輸出流PrintStream(string fileName):輸出的目的地是一個文件路徑 PrintStream extends OutputStream繼承自父類的成員方法: public void close():關閉此輸出流并釋放與此流相關聯的任何系統資源。 public void flush():刷新此輸出流并強制任何緩沖的輸出字節被寫出。 public void write(byte[] b):將b.length字節從指定的字節數組寫入此輸出流。 public void write(byte[]b,int off,int len):從指定的字節數組寫入len字節,從偏移量off開始輸出到此輸出流。 public abstract void write(int b):將指定的字節輸出流。

    注意:
    如果使用繼承自父類的write方法寫數據,那么查看數據的時候會查詢編碼表97->a
    如果使用自己特有的方法print/println方法寫數據,寫的數據原樣輸出97->97

    import java.io.FileNotFoundException; import java.io.PrintStream;public class Main{public static void main(String[] args) throws FileNotFoundException {//創建打印流PrintStream對象,構造方法中綁定要輸出的目的地PrintStream ps = new PrintStream("C:\\Users\\JunQiao Lv\\Desktop\\文件\\b.txt");//如果使用繼承自父類的write方法寫數據,那么查看數據的時候會查詢編碼表97->aps.write(97);//如果使用自己的特有方法print/println方法寫數據,寫的數據原樣輸出97->97ps.println(97);ps.println(8.8);ps.println('a');ps.println("HelloZZU");ps.println(true);//釋放資源ps.close();} }

    結果:

    PrintStream可以改變輸出語句的目的地(打印流的流向)輸出語句,默認在控制臺輸出

    使用System.setout方法改變輸出語句的目的地改為參數中傳遞的打印流的目的地
    static void setout(PrintStream out) 重新分配標準*輸出流。

    import java.io.FileNotFoundException; import java.io.PrintStream;public class Main{public static void main(String[] args) throws FileNotFoundException {System.out.println("我是在控制臺輸出的");PrintStream ps = new PrintStream("C:\\Users\\JunQiao Lv\\Desktop\\文件\\b.txt");System.setOut(ps); //把輸出語句的自的地改變為打印流的目的地System.out.println("我在打印流的目的中輸出");ps.close();} }

    結果:



    PringtStream是存在一些問題的:

  • 它的輸出與平臺有關, 對于編寫必須遵循明確協議的網絡客戶端和服務器來說是個災難
  • 它假定所在平臺的默認編碼方式,這個可能不是服務器或客戶端所期望的
  • 它吞掉了所有的異常
  • 二、以字節為單位的輸入流的框架圖

    (1)框架圖圖示

    圖示版本Jdk1.8

    (2)InputStream詳解

    它提供了寫入數據的基本方法,圈起來的是需要重點關注的:

    ?
    沒有參數的read()方法: 從輸入流的數據源中讀入一個字節數據轉為0~255的int返回, 流的結束通過返回-1來表示

    這個應與OutputStream的write(int b)方法放在一起理解

    .read()方法會等待并阻塞其后任何代碼的執行,直到有1個字節可供讀取.輸入輸出可能很慢,所以如果程序還要做其他事情,盡量把I/O放在單獨的線程中.

    read讀取一個無符號字節,注意這里要求進行類型轉換.可以通過下面的方法 將手里的有符號字節轉換成無符號字節

    int i = b>=0?b:256+b;

    ?
    另外還有兩個read() 返回-1的時候 都表示流的結束
    不結束的時候 返回值為實際讀取到的字節數

    ?
    如果不想等待所需的全部字節都立即可用,可以使用available()方法來確定不阻塞的情況下有多少字節可以讀取.它會返回可以讀取的最少字節數(事實上還能夠讀取更多的字節),但至少讀取available()建議的字節數

    ?
    在少數的情況下,可能會希望跳過數據不進行讀取.skip()方法會完成這項任務.
    與讀取文件相比,在網絡連接中它的用處不大.網絡連接是順序的,一般會很慢.所以與跳過這些數據(不讀取)相比,讀取并不會耗費太長的時間.
    文件是隨機訪問的,所以要跳過數據,可以簡單地實現為重新指定文件指針的位置,而不需要處理要跳過的各字節.

    ?
    InputStream類還有三個不太常用的方法,就是沒圈起來的

    mark()方法和reset()方法常被合起來稱之為標記與重置

    為了重新讀取數據,需要用mark()方法標記流的當前位置.在以后的某個時刻,可以用reset()方法把流重置到之前標記的位置.
    接下來的讀取操作會煩會從標記位置開始的數據.

    不過,不能隨心所欲地向前重置任意遠的位置.從標記處讀取和重置的字節數由mark()的readAheadlimit參數確定.重置太遠會有IO異常.

    一個很重要的問題是,一個流在任何時刻都只能有一個標記.標記的第二個位置會清除第一個標記.

    ?
    markSupported()方法是否返回true.如果返回true,這個流就支持標記和重置

    Java.io僅有兩個始終支持標記的輸入流類是 BufferedInputStream和ByteArrayInputStream 而其他輸入流(如TelnetInputStream)需要串聯到緩沖的輸入流才支持標記.

    TelnetInputStream在文檔中沒有展示,被隱藏在了sun包下,這個類我們是直接接觸不到的
    關于串聯其實是設計模式的思想,后面會單獨詳解

    (3)InputStream子類

    • ByteArrayInputStream 是字節數組輸入流。它包含一個內部緩沖區,該緩沖區包含從流中讀取的字節;通俗點說,它的內部緩沖區就是一個字節數組,而ByteArrayInputStream本質就是通過字節數組來實現的。
    • PipedInputStream 是管道輸入流,它和PipedOutputStream一起使用,能實現多線程間的管道通信。
    • FilterInputStream 是過濾輸入流。它是DataInputStream和BufferedInputStream的超類。
    • DataInputStream 是數據輸入流。它是用來裝飾其它輸入流,它“允許應用程序以與機器無關方式從底層輸入流中讀取基本 Java 數據類型”。
    • BufferedInputStream 是緩沖輸入流。它的作用是為另一個輸入流添加緩沖功能。
    • FileInputStream 是文件輸入流。它通常用于對文件進行讀取操作。
    • ObjectInputStream 是對象輸入流。它和ObjectOutputStream一起,用來提供對“基本數據或對象”的持久存儲。

    (4)引申:緩沖流(含字節輸出流的內容)

    BufferedOutputStream

    BufferedOutputStream類將寫入的數據存儲在緩沖區中(一個名字是buf的保護字節數組字段),直到緩沖區滿或者刷新輸出流。然后它將數據一次性寫入底層輸出流。

    這里需要好好說一下flush方法了

    關于緩沖流,如果輸出流有一個1024字節的緩沖區,未滿之前,它會一直等待數據的到達,如果是網絡發送數據的話,因為緩沖區的等待,會產生死鎖,flush()方法可以強迫緩沖的流發送數據。

    有一個細節就是當close緩沖流的時候會自動調用flush方法

    具體方法如下:

    BufferedInputStream
    值得一提的是它的構造方法:

    第一個參數是底層流,可以從中讀取未緩沖的數據。或者向其寫入數據。
    如果給出第二個參數,它會指定緩沖區的字節數,否則輸入流的緩沖區大小設置為2048字節.輸出流的緩沖區大小設置為512字節.緩沖區的理想大小取決于所緩沖的流是何種類型.

    其他方法與超類類似:

    (5)引申:數據流(含字節輸出流的內容)

    DataInputSteam和DataOutputSteam類提供了一些方法,可以用二進制格式讀/寫Java的基本數據類型和字符串.

    DataOutputSteam 寫入的每一種數據 DatainputSteam都能夠讀取.

    所有的數據都以big-endian格式寫入.

    小端格式和大端格式(Little-Endian&Big-Endian)屬于計算機組成原理的內容
    不同的CPU有不同的字節序類型,這些字節序是指整數在內存中保存的順序。
    最常見的有兩種:
    1. Little-endian:將低序字節存儲在起始地址(低位編址)
    2. Big-endian:將高序字節存儲在起始地址(高位編址)

    三、以字符為單位的輸入流和輸出流的框架圖

    (1)框架圖圖示

    • CharArrayReader 是字符數組輸入流。它用于讀取字符數組r。操作的數據是以字符為單位!

    • PipedReader 是字符類型的管道輸入流。它和PipedWriter一起是可以通過管道進行線程間的通訊。在使用管道通信時,必須將PipedWriter和PipedReader配套使用。

    • FilterReader 是字符類型的過濾輸入流。

    • BufferedReader 是字符緩沖輸入流。它的作用是為另一個輸入流添加緩沖功能。

    • InputStreamReader 是字節轉字符的輸入流。它是字節流通向字符流的橋梁:它使用指定的 charset 讀取字節并將其解碼為字符。

    • FileReader 是字符類型的文件輸入流。它通常用于對文件進行讀取操作。

    • CharArrayWriter 是字符數組輸出流。它用于讀取字符數組。操作的數據是以字符為單位!

    • PipedWriter 是字符類型的管道輸出流。它和PipedReader一起是可以通過管道進行線程間的通訊。在使用管道通信時,必須將PipedWriter和PipedWriter配套使用。

    • FilterWriter 是字符類型的過濾輸出流。

    • BufferedWriter 是字符緩沖輸出流。它的作用是為另一個輸出流添加緩沖功能。

    • OutputStreamWriter 是字節轉字符的輸出流。它是字節流通向字符流的橋梁:它使用指定的 charset 將字節轉換為字符并寫入。

    • FileWriter 是字符類型的文件輸出流。它通常用于對文件進行讀取操作。

    • PrintWriter 是字符類型的打印輸出流。它是用來裝飾其它輸出流,能為其他輸出流添加了功能,使它們能夠方便地打印各種數據值表示形式。

    (2)框架詳解

    對于Unicode文本,可以使用抽象類Reader和Writer的子類。

    小知識點: Java內置字符集是Unicode的UTF-16編碼

    很多人都將Reader及其子類稱之為 閱讀器, 將Writer及其子類稱之為書寫器,個人認為這是一個不錯的做法, 后面會探討Java io的設計模式,兩個簡稱會用到。

    Reader類的基本方法

    read()方法將一個Unicode字符(實際上是UTF-16碼點)作為一個 int返回,可以是0~65535之間的一個值,等遇到流結束的時候返回-1

    read(char[] cbuf) read(char[] cbuf,int off,int len)

    第一個方法嘗試使用字符填充數組cbuf,并返回實際讀取到的字節數,等遇到流結束的時候返回-1
    第二個方法嘗試將len個字符讀入cbuf的數組中(從off開始 長度為len)它會返回實際讀取到的字節數,如果遇到流結束的時候返回-1

    skip(Long n)等其他方法與InputStream基本上類似。

    Writer類的基本方法

    有了前面的基礎,這些api也很好理解的,你會發現Writer中有一個append方法
    它和writer方法最大區別是,append方法中的參數可以為null,而writer中的參數不能為null


    Reader和Writer最重要的具體子類是InputStreamReader和OutputStreamWirter, 以這個為例我們看一下它的實現過程,其他類類似

    關于InputStreamReader:
    它包含一個底層輸入流,可以從數據源中讀取原始字節。它根據指定的編碼方式,將這些字節轉換成Unicode字符。

    關于OutputStreamWriter
    它從運行的程序中接收Unicode字符,然后使用指定的編碼方式將這些字符轉換成字節,然后字節寫入底層輸入流中。


    有一道Java io的面試題是這樣問的:字節流和字符流之間如何相互轉換?
    其實就是我們說的這兩個具體子類,回答可以是下面的說法:
    整個IO包實際上分為字節流和字符流,但是除了這兩個流之外,還存在一組字節流-字符流的轉換類。
    OutputStreamWriter是Writer的子類,將輸出的字符流變為字節流,即將一個字符流的輸出對象變為字節流輸出對象。
    InputStreamReader是Reader的子類,將輸入的字節流變為字符流,即將一個字節流的輸入對象變為字符流的輸入對象。

    (3)引申:過濾器流

    這部分內容是我學習《Java網絡編程》第四版摘錄下來的

    Java提供了很多的過濾器類,可以附加到原始流中,在原始字節和各種格式之間進行轉換.。

    過濾器有兩種版本:過濾器流及閱讀器和書寫器。

    過濾器流仍然主要將原始數據作為字節處理,通過壓縮數據或解釋為二進制數字。閱讀器和書寫器處理各種編碼文本的特殊情況,如UTF-8和ISO 8859-1。它以鏈的形式進行組織。鏈中的每個環節都接收前一個過濾器或流的數據,并把數據傳遞給鏈中的下一個環節。在這個示例中,從本地網絡接口接收到一個加密的文本文件,在這里本地代碼將這個文件表示TelnetInputStream。通過一個BufferedInputtStream緩沖這個數據來加速整個過程。由一個CipherInputStream將數據解密。再由一GZIPInputStream解壓解密后的數據。一個InputStream將解壓后的數據轉換為Unicode文本。最后,文本由應用程序進行處理。


    這其中體現了一些設計模式

    Java io中的設計模式

    適配器模式

    關于適配器模式,這里回顧一下:

    定義: 將一個類的接口轉換成客戶期望的另一個接口

    說的通俗點就是 筆記本電腦的電源就是個適配器吧,不管它外部電壓是多少,只要經過了適配器就能轉變成我電腦需要的伏數

    作用: 使原本接口不兼容的類可以一起工作

    類型: 結構型

    演示
    (1)類適配器模式
    定義一個目標接口

    public interface Target {void request(); }

    實現接口的類

    public class ConcreteTarget implements Target{@Overridepublic void request() {System.out.println("ConcreteTarget目標方法");} }

    被適配者

    public class Adaptee {public void adapteeRequest(){System.out.println("被適配者的方法");}}

    關鍵 : 繼承被適配者且繼承目標接口

    //繼承被適配者 public class Adapter extends Adaptee implements Target{@Overridepublic void request() {super.adapteeRequest();} }

    類圖:

    測試:

    public class Test {public static void main(String[] args) {Target target = new ConcreteTarget();target.request();Target adapterTarget = new Adapter();adapterTarget.request();} }

    結果:


    (2)下面來看一下對象適配器模式
    和類適配器模式相比 只有一處發生了變化

    Adapter 類 之前還繼承了Adaptee,現在沒有繼承

    //繼承被適配者 public class Adapter implements Target{private Adaptee adaptee = new Adaptee();@Overridepublic void request() {adaptee.adapteeRequest();} }

    類圖:

    前面已經說了字節流和字符流之間的相互轉換靠的是OutputStreamWriter和InputStreamReader,其實它們充當了適配器,如果不相信可以翻源碼。

    這里舉個轉換的例子:

    BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("test.txt"))));

    裝飾者模式

    同樣回顧一下:

    定義:在不改變原有對象的基礎之上,將功能附加到對象上

    作用: 提供了比繼承更有彈性的替代方案(擴展原有對象功能)

    類型: 結構型

    使用場景:

  • 擴展一個類的功能或給一個類添加附加職責
  • 動態的給一個對象添加功能,這些功能可以再動態的撤銷
  • 演示

    不滿足裝飾者模式的情況
    以買煎餅為例

    public class Battercake {protected String getDesc(){return "煎餅";}protected int cost(){return 5;} }

    煎餅 加一個雞蛋

    public class BattercakeWithEgg extends Battercake{@Overrideprotected String getDesc() {return super.getDesc()+"加一個雞蛋";}@Overrideprotected int cost() {return super.cost()+ 1;} }

    煎餅加一個雞蛋 加一個烤腸

    public class BattercakeWithEggSausage extends BattercakeWithEgg{@Overrideprotected String getDesc() {return super.getDesc()+"加一根香腸";}@Overridepublic int cost() {return super.cost()+2;} }

    測試:

    public class Test {public static void main(String[] args) {Battercake battercake = new Battercake();System.out.println(battercake.getDesc()+ " 銷售價格為:"+battercake.cost());BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();System.out.println(battercakeWithEgg.getDesc()+ " 銷售價格為:"+battercakeWithEgg.cost());BattercakeWithEggSausage battercakeWithEggSausage = new BattercakeWithEggSausage();System.out.println(battercakeWithEgg.getDesc()+ " 銷售價格為:"+battercakeWithEgg.cost());} }

    我們看一下它的UML類圖 很容易發現 單純的繼承關系

    如果一個煎餅加兩個雞蛋 或者別的其他的呢 容易引起"類爆炸"

    所以為了解決這個問題 引入裝飾著模式,下面讓我們看一下滿足裝飾者模式的寫法

    這里被裝飾的是煎餅 裝飾者是雞蛋和香腸

    創建煎餅的抽象類

    public abstract class ABattercake {protected abstract String getDesc();protected abstract int cost(); }

    創建煎餅

    public class Battercake extends ABattercake{protected String getDesc(){return "煎餅";}protected int cost(){return 5;} }

    創建裝飾著的抽象類

    public class AbstractDecorator extends ABattercake{private ABattercake aBattercake;public AbstractDecorator(ABattercake aBattercake) { //注入煎餅this.aBattercake = aBattercake;}@Overrideprotected String getDesc() {return this.getDesc();}@Overrideprotected int cost() {return this.cost();} }

    雞蛋的裝飾者

    public class EggDecorator extends AbstractDecorator {public EggDecorator(ABattercake aBattercake) {super(aBattercake);}@Overrideprotected String getDesc() {return super.getDesc()+"加一個雞蛋";}@Overrideprotected int cost() {return super.cost()+1;} }

    香腸的裝飾者

    public class SausageDecorator extends AbstractDecorator{public SausageDecorator(ABattercake aBattercake) {super(aBattercake);}@Overrideprotected String getDesc() {return super.getDesc()+"加一根香腸";}@Overrideprotected int cost() {return super.cost()+1;} }

    類圖:

    測試:

    public class Test {public static void main(String[] args) {ABattercake aBattercake;aBattercake = new Battercake();aBattercake = new EggDecorator(aBattercake);aBattercake = new EggDecorator(aBattercake);aBattercake = new SausageDecorator(aBattercake);System.out.println(aBattercake.getDesc() + " 銷售價格為:" +aBattercake.cost());} }

    結果:

    類圖:

    嵌套的風格有沒有讓你回憶出點什么?

    這個時候我們就需要關注過濾器流了 FilterInputStream和 FilterOutputStream,這里以FilterInputStream為例:

    舉個例子

    DataInputStream dataInputStream = new DataInputStream(new FileInputStream(new File("text.txt")));

    其中DataInputStream給FileInputStream起到包裝作用。

    Java的IO流裝飾模式很明顯體現在這兩點:
    1、有原數據、可以被包裝的。
    2、包裝類,能包裝其他事物的,就是在原事物上添加點東西。


    復盤

    文章內容是有點多的,其實重要的是一定要理解這幾個概念:

  • Java的IO建立在流之上,它的體系建立在InputStream和OutputStream抽象類的基礎上,這兩個抽象類的基本方法被子類拓展實現了針對某一數據源的處理,因此一定要對它們的基本方法有著清晰的認識。尤其注意它們用于處理字節。
  • 在InputStream和OutputStream基礎上,又根據實現的具體子類的功能劃分出了緩沖流、數據流等。
  • 在它們的子類中還有過濾器流FilterInputStream和 FilterOutputStream,它們屬于裝飾者模式中的裝飾者,拓展了被裝飾者的功能。
  • Writer和Reader被稱為書寫器和閱讀器,它的具體子類InputStreamReader和OutputStreamWriter實現了字節到字符的過渡,運用了適配器模式的設計模式。
  • Java IO涉及的東西還并未結束,畢竟它是Java的一些高級技術的基石,而且也是一些大廠面試必不可少的東西,打得越牢固越好。下面要么再寫一篇,要么在該篇繼續補充,歡迎各位拍磚~

    總結

    以上是生活随笔為你收集整理的【Java核心技术卷】I/O详析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。