[二十六]JavaIO之再回首恍然(如夢? 大悟?)




流分類回顧


本文是JavaIO告一段落的總結帖
希望對JavaIO做一個基礎性的總結(不涉及NIO)
從實現的角度進行簡單的介紹

下面的這兩個表格,之前出現過
數據源形式 InputStream OutputStream Reader Writer
ByteArray(字節數組) ByteArrayInputStream  ByteArrayOutputStream 
File(文件) FileInputStream  FileOutputStream  FileReader FileWriter 
Piped(管道) PipedInputStream  PipedOutputStream  PipedReader  PipedWriter 
Object(對象) ObjectInputStream  ObjectOutputStream 
String StringBufferInputStream  StringReader  StringWriter 
CharArray(字符數組) CharArrayReader  CharArrayWriter 

擴展功能點 InputStream OutputStream Reader Writer
Data(基本類型) DataInputStream   DataOutputStream 
Buffered(緩沖) BufferedInputStream  BufferedOutputStream  BufferedReader  BufferedWriter   
LineNumber(行號) LineNumberInputStream LineNumberReader 
Pushback(回退) PushbackInputStream  PushbackReader 
Print(打印) PrintStream  PrintWriter


流分為輸入輸出流兩種形式
數據格式又分為字節和字符兩種形式
他們組合而來了四大家族
InputStream        OutputStream        Reader        Writer
所有的四大家族的流有兩種合成擴展方式:
按照數據源形式擴展
按照裝飾功能點擴展



數據源形式擴展


現在我們換一個維度,從實現的角度,重新介紹下IO

數據源擴展的根本
從這種形式的數據中讀取數據
寫入數據到這種數據形式

我們上面列出來了ByteArray  File   Piped    Object  String  CharArray 這幾種常用的數據源形式
結合我們上面的概念,我們看一下,實際的實現

字節數組 / 字符數組 /String

ByteArray
內存數據
ByteArrayInputStream 內部有一個byte buf[] 引用
指向實際保存數據的那個字節數組
ByteArrayInputStream(byte buf[])
ByteArrayInputStream(byte buf[], int offset, int length)
構造方法將內部的byte buf[] 引用指向某個 byte buf[]
然后就從這里讀
ByteArrayOutputStream 內部有一個byte buf[]緩沖區 構造方法初始化這個緩沖區,也就是分配空間
數據的寫,就是寫到這里面


CharArray
內存數據
CharArrayReader 內部有一個 char buf[]; 引用
指向實際保存數據的那個字符數組
CharArrayReader(char buf[])
public CharArrayReader(char buf[], int offset, int length)
構造方法將內部的char buf[]; 引用指向某個char buf[]
然后就從這里讀
CharArrayWriter 內部有一個char buf[] 緩沖區
構造方法初始化這個緩沖區,也就是分配空間
數據的寫,就是寫到這里面



String
內存數據
StringReader 內部有一個 String str;引用
指向實際的那個提供數據的String
StringReader(String s)
構造方法將內部的str   引用指向某個String
然后就從這里讀
StringWriter 內部有一個StringBuffer buf 用於保存數據
public StringWriter()
StringWriter(int initialSize)
構造方法初始化這個StringBuffer,就是new StringBuffer時可以指定大小 
數據的寫,就是寫到這里面  也就是StringBuffer.append

上面的這三種數據源形式,從上面看的話,邏輯非常清晰
讀--->從哪里讀?--->你給我一個數據源--->我以IO的操作習慣(InputStream/Reader) 讀給你
寫--->IO的操作習慣寫(OutputStream/Writer) --->寫到哪里?--->寫到我自己內部的存儲里

有人可能覺得寫到你自己內部存儲里面干嘛,有毛用?

ByteArrayOutputStream image_5b9b6870_54fc
CharArrayWriter image_5b9b6870_1ff1
StringWriter image_5b9b6870_5824

內存數據,如果僅僅是存起來放到他自己肚子里面當然毛用沒有
但是,他們都提供了吐出來的功能了
給[字節數組 字符數組  String] 提供了一個統一的一致性的讀寫形式,操作非常方便,不是么

image_5b9b6870_7808
真實數據使用引用指向
內部存儲是內部的存儲區



管道

pipe 管道用於直連 然后進行數據的傳輸
主要用於多線程數據共享

In 輸入管道里面有一個存儲區
Out 輸出管道內有個In的引用

Connect之后,In指向了某個實際的 輸入流

然后Out通過引用操作In里面的存儲區
In自己的讀方法也是操作這個存儲區
image_5b9b6870_f2f


Pipe
PipedInputStream 內部有存儲區byte buffer[]  PipedInputStream()
PipedInputStream(int pipeSize)
構造方法中給存儲區分配空間,可以指定存儲區的大小,否則默認值
PipedOutputStream 內部有一個PipedInputStream sink 引用 PipedOutputStream()
PipedOutputStream(PipedInputStream snk)
無參的后續還需要調用connect
有參數的創建對象時就進行connect連接
PipedReader 內部有存儲區 char buffer[];   PipedReader()
PipedReader(int pipeSize)
構造方法中給存儲區分配空間,可以指定大小,否則默認值
PipedWriter 內部有一個PipedReader sink;  引用 PipedWriter()
PipedWriter(PipedReader snk)
無參的后續還需要調用connect
有參數的創建對象時進行connect連接


所以一旦理解了,JavaIO管道的模型,管道就實在是太簡單了
image_5b9b6870_4d4f
只需要記住:
輸入In里面 有一個存儲緩沖區, 輸出有一個引用指向了In
connect將他們連接起來,他們共同操作一個池子
輸出往里面寫,輸入從里面讀
管子的方向只能是 :    輸出 -----> 輸入





文件
文件相關的,都是實實在在的要通過操作系統了
所以也就必然需要使用本地方法

在Java中一個文件使用File來描述,File是抽象路徑名 可以表示文件  也可以表示目錄
File可以通過String路徑名構造
另外還有文件描述符可以表示指代文件


File
磁盤數據
FileInputStream 操作文件
構造方法可以使用:  File /String的路徑名 /文件描述符   來創建
實實在在的一個InputStream的實現類,最終通過本地方法來進行數據讀取
FileOutputStream 操作文件
構造方法可以使用: File/ String的路徑名 /文件描述符     來創建
另外他還有是否追加的概念
實實在在的一個OutputStream的實現類,最終通過本地方法來進行數據寫入


底層文件本身是二進制存儲的,如果你想要通過字符去操作文件,必然要經過 編碼和解碼的過程
image_5b9b6870_6d14
JavaIO提供了InputStreamReader    OutputStreamWriter兩個轉換流來實現編碼和解碼
想要徹底理解還是需要理解適配器模式
這兩個流都是字符和字節的轉換,只不過是方向不同
從字節到字符,這就是解碼  ;   從字符到字節,這就是編碼
InputStreamReader   字節流到字符流的橋梁, 也就是解碼   從上圖看,二進制才是碼,從碼到字符
OutputStreamWriter 字符流到字節流的橋梁, 也就是編碼   從上圖看,二進制才是碼,從字符到碼


根據上面的說法,FileReader 和 FileWriter必然要是一種轉換流

File
磁盤數據
FileReader
文件本身都是二進制序列 字節形式,所以必然有字節InputStream到字符Reader的轉換
而InputStreamReader 恰恰是字節通向字符的橋梁
所以 FileReader繼承了InputStreamReader  是一種特殊的InputStreamReader

InputStreamReader 將InputStream適配成Reader   所以需要InputStream作為參數進行構造
文件的字節輸入流--FileInputStream可以使用:  File /String的路徑名 /文件描述符  來創建
所以FileReader的構造方法接受這幾種參數, 構造一個FileInputStream
然后調用InputStreamReader 的構造方法

InputStreamReader字節到字符 解碼 涉及到碼表  InputStreamReader構造方法部分需要指定編碼
FileWriter
文件本身都是二進制序列 字節形式,所以想要寫入數據必然有字符Writer到字節OutputStream的轉換
而OutputStreamWriter 恰恰是字符通向字節的橋梁
所以 FileWriter繼承了OutputStreamWriter  是一種特殊的OutputStreamWriter

OutputStreamWriter 將OutputStream適配成Writer   所以需要OutputStream作為參數進行構造
文件的字節輸出流 --FileOutputStream可以使用:  File /String的路徑名 /文件描述符   來創建
所以FileWriter的構造方法接受這幾種參數
然后構造一個FileOutputStream,調用OutputStreamWriter 的構造方法

另外FileOutputStream 還有追加的概念
所以FileWriter 的構造方法里面也有append 追加這一項

OutputStreamWriter字符到字節 編碼 涉及到編碼 OutputStreamWriter構造方法部分需要指定編碼

關於FileReader 和FileWriter說了那么多
其實只有兩句話,就是字節流與字符流之間進行轉換
Reader reader = new InputStreamReader( new FileInputStream(.......));
Writer writer = new OutputStreamWriter( new FileOutputStream(.......));



Object
對於文件中的字符與字節的轉換,可以通過某些編碼表,查表法確定編碼的值,進而完成字符與字節之間的相互轉換
那么,對於一個對象呢?
Object是內存中的數據,他並不是一串字符形式
有一個概念叫做         序列化與反序列化
其實就了類似  字符的編碼與解碼
image_5b9b6870_3444
從這個圖應該能感知到ObjectInputStream和ObjectOutputStream    與 字符流的邏輯類似么
字符與字節轉換 是一種  編碼解碼的過程
對象序列化與反序列化 不也是一種編碼解碼的過程嗎 ,只不過這個編碼解碼不是單純的查詢碼表這么簡單
字符流與字節流的轉換,可以通過轉換流進行處理
Object序列化與反序列化是
ObjectInputStream  ObjectOutputStream  他們分別實現ObjectInput 和 ObjectOutput來提供的
所以從這個角度講的話,可以把Object理解成為一種很特殊的"字符"
他們兩個就像InputStreamReader  和 OutputStreamWriter似的,用來轉換
 

功能的裝飾擴展

既然是功能的裝飾擴展,我們之前已經說過很多次,都是裝飾器模式
也就是說了很多遍的
是你還有你,一切拜托你,中間增加點功能
這個,就是需要被裝飾的抽象角色Component   
就是這四大家族      InputStream        OutputStream        Reader        Writer   
給讀和寫裝飾增加新的功能,也就是最根本的讀和寫方法,將都是使用ConcreteComponent
在基本的讀和寫方法之上,提供了新的功能

Data
DataInputStream 繼承自FilterInputStream
得到一個InputStream引用in

構造方法需要InputStream
通過in.read系列方法讀取,   然后將讀取的數據    組裝成基本數據類型
進而提供讀取基本數據類型的能力
DataOutputStream 繼承自FilterOutputStream
得到一個OutputStream 引用out

構造方法需要OutputStream
將基本類型數據進行轉化處理,    然后調用out.write系列方法將數據寫入
進而提供寫入基本數據類型的能力


緩沖的概念都是內部有一個緩沖區
緩沖輸入  是通過底層的流往自己的緩沖區寫入數據, 應用程序從緩沖輸入的緩沖區中讀取,提高了read速度
緩沖輸出  是把數據寫入到自己的緩沖區中,后續再把數據通過底層的流一並寫入,從而提高了write的速度
因為讀寫都是從緩沖區中進行的了
Buffered
BufferedInputStream
繼承自FilterInputStream
得到一個InputStream引用in
構造方法需要InputStream

內部有一個緩沖區byte buf[]
BufferedOutputStream
繼承自FilterOutputStream
得到一個OutputStream 引用out
構造方法需要OutputStream

內部有一個緩沖區buf[];
BufferedReader 內部有Reader 引用 in
構造方法需要一個Reader

內部有一個緩沖區char cb[];
BufferedWriter 內部有一個Writer 引用  out
構造方法需要一個Writer

內部有一個緩沖區char cb[];

LineNumberReader
內部使用了一個lineNumber = 0;  用來記錄行號
這個行號可以使用方法設置和獲取
getLineNumber  setLineNumber  但是他不改變流的位置

PushBack
裝飾器模式 方法依賴於被裝飾的實體 ConcreteComponent
只是內部有一個緩沖區,可以存放被回退掉的字符
所有的讀取方法在進行讀取的時候,都會查看緩沖區的數據
PushbackInputStream

繼承自FilterInputStream 得到一個InputStream 引用in
構造方法需要 InputStream

內部有緩沖區byte[] buf
FilterReader 繼承自FilterReader 得到一個Reader引用 in
構造方法需要一個Reader

內部有緩沖區char[] buf


Print
提供了多種形式的打印,根本只是在真的寫入數據前,將數據參數進行一些處理
根本的寫操作 依賴被裝飾的節點流提供
在數據寫入之前進行必要的數據處理
PrintStream 繼承自 FilterOutputStream得到一個OutputStream 引用 out
構造需要一個OutputStream
PrintWriter 內部有一個out
構造方法需要一個Writer


所以你看,擴展的功能通過裝飾器模式,他們的行為都是類似的,那就是:
1. 最基本的讀寫依賴被裝飾的具體的節點流
2. 然后進行了功能的增強


總結

說到這個地方,我們又從實現的角度把常用的一些流進行了介紹
你會發現看起來那么多,實際上並沒有多少
四大家族,以及幾種數據源形式,以及幾個擴展功能點
只要找准了思路,理清楚了邏輯,就不難理解了

不知道到底是恍然大悟?還是?恍然如夢 ?






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM