終於結束了集合的學習,今天咱們就開始學習 I/O的操做了。 I/O 系列的內容分爲 I/O概述、字符流、字節流。今天要學的是 I/O和字符流的操做設計模式
因爲概述篇幅較短,因此就把概述壓縮到這裏來了。bash
I/O:即 Input Output,輸入輸出的意思。app
對數據的操做,其實就是對 File 文件。我偷了一張祖師爺傳下來的圖來描述 IO 流類結構關係。函數
從圖中能夠看出,都是從這如下四個類中派生出來的子類,子類的類型也好區分,後綴都是抽象基類名。性能
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: 用於讀取字符流的抽象類。子類必須實現的方法只有 read(char[], int, int) 和 close()。可是,多數子類將重寫此處定義的一些方法,以提供更高的效率和/或其餘功能。this
Writer: 寫入字符流的抽象類。子類必須實現的方法僅有 write(char[], int, int)、flush() 和 close()。可是,多數子類將重寫此處定義的一些方法,以提供更高的效率和/或其餘功能。編碼
先看看基本運用吧~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 文件裏面。
同時,這步操做也能夠當成是文件的拷貝。
須要注意的是:
若是要實現追加寫入文件的操做,好比將 raw1.txt 和 raw2.txt 共同寫入 target.txt 裏面,只須要在建立 FileWriter 的時候使用兩個參數的構造方法便可,如:FileWriter fileWriter = new FileWriter("target.txt",true);
至於源碼讀寫的實現,我簡單說一下吧。其實 FileReader/FileWriter 的構造方法裏面都有 new FileInputStream/FileOutputStream 的對象,而後繼承的是InputStreamReader/OutputStreamWriter.這說明啥,我想你們內心應該有數了吧,其實仍是調用了字節流的讀取,而後使用StreamEncoder/StreamDecoder進行編碼解碼操做。
具體流程是這樣的,這裏每次讀/寫
太長了,我只說讀取操做了,反正都是相對的。
字符流的緩衝區,提升了對數據的讀寫效率,他有兩個子類
緩衝區要結合流纔可使用
在流的基礎上對流的功能進行了加強
源碼就不帶着你們一塊兒讀了,我給你們分析一下 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 方法,能夠一次讀取一行字符串。
其實這個一次讀取一行字符串仍是蠻有用的,好比說讀取一些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 寫的一個包裝類。
大家先感覺一下這個類的用法
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模式相比生成子類更爲靈活。不改變接口的前提下,加強所考慮的類的性能。