0.0 為什么要寫這個。
- 在當初學
java語言
的時候,其實感覺這算是最難的基礎部分內容之一,因為字符流和字節流
的存在,還有緩沖字節流、字符流
,選擇太多,不同的限制,導致使用的時候根本不知道:
1. 到底什么情況下怎么寫
2. 什么方案和代碼,寫會沒有什么大的問題。
- 所以在這里寫下相關知識點,所謂
授人以漁
,更要授人以鱗、 鯉、 鯽、 鯨、 鰭、 鰲、 鰓、 鱖、 鱸、 鮭、 鯀、 鯤、 鯡、 鯫、 鯢、 鮒、 鱒、 鳒、 鰣、 鲝、 鲹、 鯖、 鲉、 鰈、 鳀、 鮐、 鯁、 鳑、 鳛、 鲞、 鲬、 鰉、 鰱、 鯪、 鰩、 鮪……
3.本來一個內容就打算寫一篇的,簡書說我寫得太長了,不許我發布,所以拆成兩篇,查閱本篇的朋友請結合另一篇一同參考,謝謝。
鏈接如下:
【Java】1.0 Java核心之IO流(一)——生猛理解字節流
8.0 字符流
字符流內容用的機會相當少,接著往下看你就會發現,也不太方便用,所以盡量少地講解,但是會有幾個有趣的算法實現,通過單獨的一篇文章記錄,也算是對IO流的一些綜合運用。
【鏈接暫空】
8.1.字符流是什么
- 字符流是可以直接讀寫字符的IO流
- 字符流讀取字符, 就要先讀取到字節數據, 然后轉為字符。 如果要寫出字符, 需要把字符轉為字節再寫出.
8.2 首先要普及一下中文字符的相關知識點
舉個例子,我們中文用的碼表,一般是GBK碼表(GBK碼表是java平臺默認的中文編碼表,我們常用的UTF-8碼表中通常1個中文字符代表3個字節)
- GBK碼表是1個中文分為2個字節,第1個字節一定是個負數,第2個字節是正數
- 計算機讀取的時候,雖然也是1個字節1個字節地讀取,但是它會讀到下一個負數字節,停頓,然后兩個字節、兩個字節的拼接在一起讀取,這就是字符流讀取數據的原理。
- 別試圖去百度GBK碼表和UTF-8碼表的原理,光介紹的那一兩千字講暈你懷疑人生信不信。
8.3 字符流的抽象父類:
Reader
Writer
我們從這里開始講起,其實正常的出招方式如下代碼:
FileReader fr = new FileReader("aaa.txt"); //創建輸入流對象,關聯aaa.txt
int ch;
while((ch = fr.read()) != -1) { //將讀到的字符賦值給ch
System.out.println((char)ch); //將讀到的字符強轉后打印
}
fr.close(); //關流
這里aaa.txt默認創建就好,里面隨便寫些中文,別設置成utf-8編碼方式,小心亂碼懷疑人生。
寫出的出招方式如下:
FileWriter fw = new FileWriter("aaa.txt");
fw.write("大家好,我是渣渣輝。");
//97輸出后,在aaa.txt文件中會變成a,
//因為字符流先處理字符問題,再轉成字節流輸出,所以它同樣經歷了字節流的處理
fw.write(97);
fw.close();
拷貝的出招方式(就是上面的合在一起,先讀取后再寫出)如下:
FileReader fr = new FileReader("a.txt");
FileWriter fw = new FileWriter("b.txt");
int ch;
while((ch = fr.read()) != -1) {
fw.write(ch);
}
fr.close();
fw.close();
一看就知道,還是熟悉的配方還是熟悉的套路。但是!
劃線重點來了。
8.31 原理,分析原理。
舉個例子,字符流的抽象父類 Reader
,這個叫字符輸入流,因為抽象,所以不能用它來實例化對象,我們來查看JDK API文檔:
可以看到,它繼承自Object包,直接子類也不多。
- 在字節流里面,
InputStream
類是爸爸,但我們可以看到,實際寫代碼時一般都是兒子FileInputStream
在干活。
同樣字符流的爸爸Reader
類,我們可以試著找一找有沒有個兒子叫FileReader
類,這樣我們就可以照葫蘆畫瓢,這一小節也就不用學了,但是我們會發現——沒有!
我們找其中一個InputStreamReader
類,試試看:
哎!你會發現它就只有一個直接子類
FileReader
類,不著急,我們再看下這個孫子FileReader
類:
這可不是我截圖不全,已經是全部了。會發現,孫子地位不行,能力也沒有,全靠繼承老爹
InputStreamReader
類(雖然它爹只生了一個兒子)和爺爺、太爺爺的各種方法。
我們平時要用的字符流,就是在用這個孫子FileReader
類。
那為什么本來是兒子的地位淪落到孫子的地位,還這么慘?
這里涉及我們上面所說的裝飾者模式。(我們先不著急說明什么是裝飾者模式,暫且不表)
8.32 在寫出的時候,如下代碼:
FileWriter fw = new FileWriter("aaa.txt");
fw.write("大家好,我是渣渣輝。");
//97輸出后,在aaa.txt文件中會變成a,
//因為字符流先處理字符問題,再轉成字節流輸出,所以它同樣經歷了字節流的處理
fw.write(97);
fw.close();
如果不寫fw.close();
,執行會發現,什么也沒有存入。
哎不對呀!還學沒到緩沖字符流的地步,怎么不執行fw.close();
就不行了,FileWriter
類 明顯不按套路出牌。
我們查看FileWriter
類源代碼:
這里就不貼代碼了,里面同樣看不出什么來,繼續看它的繼承父類OutputStreamWriter
的源代碼:
同樣看不出什么來,不急,繼續看它的繼承父類
Writer
的源代碼:哎!這里有了,雖然不是緩沖流,但是它自帶了一個小緩沖
writeBuffer
,而且大小是1024,注意了,這里的1024不是1024個字節,而是1024個字符 = 2048個字節。所以如果用字符流的
FileWriter
類和FileReader
類而不去關閉它們,就會丟失數據。
8.4 類比一下,同樣字符流也可以像字節流一樣,可以不必一個字符一個字符地讀和寫,自定義小數組:
public static void demo() throws FileNotFoundException, IOException {
FileReader fr = new FileReader("xxx.txt");
FileWriter fw = new FileWriter("yyy.txt");
char[] arr = new char[1024];
int len;
while((len = fr.read(arr)) != -1) { //將文件上的數據讀取到字 ···符數組中
fw.write(arr,0,len); //將字符數組中的數據寫到文件上
}
fr.close();
fw.close();
}
你看,字符流就這些東西,完畢。
9.0 緩沖字符流
9.1 緩沖字符流
同樣,目的還是為了提高效率,才會有緩沖字符流,如果你用字符流并不需要用到這樣的功效,那么大可不必使用緩沖字符流,用字符流就可以了。
-
BufferedReader
的read()方法
讀取字符時會一次讀取若干字符到緩沖區, 然后逐個返回給程序, 降低讀取文件的次數, 提高效率。 -
BufferedWriter
的write()方法
寫出字符時會先寫到緩沖區, 緩沖區寫滿時才會寫到文件, 降低寫文件的次數, 提高效率。
9.2 用法同樣很簡單,反正都是3步走:
BufferedReader br = new BufferedReader(new FileReader("aaa.txt")); //創建字符輸入流對象,關聯aaa.txt
BufferedWriter bw = new BufferedWriter(new FileWriter("bbb.txt")); //創建字符輸出流對象,關聯bbb.txt
int ch;
//read一次,會先將緩沖區讀滿,從緩沖去中一個一個的返給臨時變量ch
while((ch = br.read()) != -1) {
//write一次,是將數據裝到字符數組,裝滿后再一起寫出去
bw.write(ch);
}
//關流
br.close();
bw.close();
其原理和字節流一模一樣。
9.3 細節來了。字符流中有一些方法,比較有意思,可以加以了解。
public static void demo() throws FileNotFoundException, IOException {
BufferedReader br = new BufferedReader(new FileReader("zzz.txt"));
String line;
while((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
- 關于這段代碼,我們可以去查看文檔,在我們的緩沖字符輸入流
BufferedReader
中有一個readLine()
方法,作用是讀取一個文本行。我們可以去查看這個方法的源碼,你就會發現自己居然也可以為java語言貢獻一個方法出來。
2019-03-10_003324.png
注意了,我們的緩沖字符輸入流BufferedReader
中的readLine()
方法,讀取后,相應的換行、回車符(\r
、\n
)就丟了,如果你常試直接寫出的話,會發現所有的內容都會在同一自然段全部寫完。
聰明的朋友已經猜到了,沒錯我們的緩沖字符輸出流BufferedWriter
中除了有一個 write()
方法,查看文檔還發現出現一個專門換行的方法newLine()
:
BufferedReader br = new BufferedReader(new FileReader("zzz.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("aaa.txt"));
String line;
while((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); //寫出回車換行符
//bw.write("\r\n");
}
br.close();
bw.close();
當你需要這樣去讀取文件時,上面就是可以copy的模板了。
細節又來了。我們的注釋語句里面有一句//bw.write("\r\n");
,我們可以看到,newLine()
這個方法我們不可以直接通過注釋中那樣編寫代碼也能實現嗎?為什么java要這么累贅一下。
我們不要忘了,在windows操作環境下,他們的確是一樣的,但不同的是newLine()
方法可以兼容所有JVM運行的平臺。
9.4 我們的緩沖字符輸入流BufferedReader
,還有一個獨生子。
-
LineNumberReader
跟蹤行號的緩沖字符輸入流。繼承自BufferedReader
父類。意思就是說,它和BufferedReader
具有相同的功能, 一模一樣,只不過是多了個可以統計行號的技能。 -
BufferedWriter
注意!緩沖字符輸出流BufferedWriter
并沒有親兒子,其他緩沖流也沒有,都是單身狗。
public static void main(String[] args) throws IOException {
LineNumberReader lnr = new LineNumberReader(new FileReader("zzz.txt"));
String line;
lnr.setLineNumber(100);
while((line = lnr.readLine()) != null) {
System.out.println(lnr.getLineNumber() + ":" + line);
}
lnr.close();
}
如上,你可以常試這樣去用。我們先把lnr.setLineNumber(100);
注釋掉。
可以看到輸出會從第0行開始,加冒號:
,然后加具體哪一行的內容,我們可以查看LineNumberReader
類的源代碼:
可以看到里面有一個變量
lineNumber
初始值是0,并提供了get( )方法
和set( )方法
。所以,當我們把
lnr.setLineNumber(100);
注釋去掉時,會發現輸出語句的記錄行數會從第100行開始,就這么個功能,需要就用。而且這樣功能的類只有它有,其他緩沖流都沒有。
這一部分其實就這么寫內容,完畢,更加深入的運用,上面也給出一個另一篇文章的鏈接,里面會深入講解IO流的使用。粘貼下來:
【鏈接暫空】
10.0 裝飾者模式
當然,我到現在依然分不明白23種設計模式,都不一定全能默寫出來,更別說分類了。
裝飾者模式通俗理解為
:小情人Mary過完輪到Sarah過生日,還是不要叫她自己挑了生日禮物,不然這個月伙食費肯定玩完,拿出我去年在步行街照的照片,在背面寫上“最好的的禮物,就是愛你的老剛”,再到街上禮品店買了個像框(賣禮品的MM也很漂亮哦),再找隔壁搞美術設計的Mike設計了一個漂亮的盒子裝起來……,我們都是裝裱匠,最終都在裝飾我這個人。
開個玩笑。我們這里當然是講解裝飾者模式,主要是基于IO流來說的,畢竟它基本上就是基于裝飾者模式弄出來的。舉個例子:
interface Coder {
public void code();
}
class Student implements Coder {
@Override
public void code() {
System.out.println("javase");
System.out.println("javaweb");
}
}
class JuanLanMenStudent implements Coder {
//1,獲取被裝飾類的引用
private Student s; //獲取學生引用
//2,在構造方法中傳入被裝飾類的對象
public JuanLanMenStudent (Student s) {
this.s = s;
}
//3,對原有的功能進行升級
@Override
public void code() {
s.code();
System.out.println("ssh");
System.out.println("數據庫");
System.out.println("大數據");
System.out.println("...");
}
}
有一種編碼能力,剛出道的大學生,可能就學了點javase
和javaweb
,發現畢業后工作不好找,工資不咋地。于是決定拜山學藝,就拜入卷簾門當學生。于是他在卷簾門還學了ssh
、數據庫
、大數據
、高速路發傳單等等等等
,這樣對原來的大學生進行裝飾,他就是卷簾門出品的大學生那畢業后就了不得了。
所以,重點又來了!——裝飾設計模式的好處是:
* 耦合性不強,被裝飾的類的變化與裝飾類的變化無關,子類的改變不會引起父類的改變。
11.0 下面講的東西怕看多了搞混,所以單拿出來寫最后,注意:這里面的功能都是字符流里面的特例了,和字節流沒有什么共同特性(字節流里面沒有類似的功能、方法)
11.1 使用指定的碼表讀寫字符
-
FileReader
是使用默認碼表讀取文件, 如果需要使用指定碼表讀取, 那么可以使用InputStreamReader(字節流,編碼表)
-
FileWriter
是使用默認碼表寫出文件, 如果需要使用指定碼表寫出, 那么可以使用OutputStreamWriter(字節流,編碼表)
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf-8.txt"), "uTf-8"); //指定碼表讀字符
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"), "gbk"); //指定碼表寫字符
int c;
while((c = isr.read()) != -1) {
osw.write(c);
}
isr.close();
osw.close();
**細節:
- 1.0 ** 這里的
"UTF-8"
、"GBK"
里面的英文字母大小寫隨意,還可以大小寫亂搭,只要你字母順序別打錯就行了。 - 2.0 這里發現沒,終于用了
InputStreamReader
和OutputStreamWriter
類,很多人有疑問,比如InputStreamReader
類前半段是字節流的InputStream
,后半段是字符流的Reader
,那它到低算字節流還是字符流?
其實我們查看源代碼或者查看Java API文檔,就知道InputStreamReader
類繼承自Reader
類,當然是根正苗紅的字符流了。 - 3.0 上面我們當然還可以優化,畢竟緩沖流的存在不就是為了這個么:
BufferedReader br =//更高效的讀
new BufferedReader(new InputStreamReader(new FileInputStream("utf-8.txt"), "uTf-8"));
BufferedWriter bw =//更高效的寫
new BufferedWriter(new OutputStreamWriter(new FileOutputStream("gbk.txt"), "gbk"));
int c;
while((c = br.read()) != -1) {
bw.write(c);
}
br.close();
bw.close();
END