0.0 為什么要寫這個(gè)。
- 在當(dāng)初學(xué)
java語言的時(shí)候,其實(shí)感覺這算是最難的基礎(chǔ)部分內(nèi)容之一,因?yàn)?strong>字符流和字節(jié)流的存在,還有緩沖字節(jié)流、字符流,選擇太多,不同的限制,導(dǎo)致使用的時(shí)候根本不知道:
1. 到底什么情況下怎么寫
2. 什么方案和代碼,寫會(huì)沒有什么大的問題。
- 所以在這里寫下相關(guān)知識(shí)點(diǎn),所謂
授人以漁,更要授人以鱗、 鯉、 鯽、 鯨、 鰭、 鰲、 鰓、 鱖、 鱸、 鮭、 鯀、 鯤、 鯡、 鯫、 鯢、 鮒、 鱒、 鳒、 鰣、 鲝、 鲹、 鯖、 鲉、 鰈、 鳀、 鮐、 鯁、 鳑、 鳛、 鲞、 鲬、 鰉、 鰱、 鯪、 鰩、 鮪……
3.本來一個(gè)內(nèi)容就打算寫一篇的,簡(jiǎn)書說我寫得太長(zhǎng)了,不許我發(fā)布,所以拆成兩篇,查閱本篇的朋友請(qǐng)結(jié)合另一篇一同參考,謝謝。
鏈接如下:
【Java】1.0 Java核心之IO流(一)——生猛理解字節(jié)流
8.0 字符流
字符流內(nèi)容用的機(jī)會(huì)相當(dāng)少,接著往下看你就會(huì)發(fā)現(xiàn),也不太方便用,所以盡量少地講解,但是會(huì)有幾個(gè)有趣的算法實(shí)現(xiàn),通過單獨(dú)的一篇文章記錄,也算是對(duì)IO流的一些綜合運(yùn)用。
【鏈接暫空】
8.1.字符流是什么
- 字符流是可以直接讀寫字符的IO流
- 字符流讀取字符, 就要先讀取到字節(jié)數(shù)據(jù), 然后轉(zhuǎn)為字符。 如果要寫出字符, 需要把字符轉(zhuǎn)為字節(jié)再寫出.
8.2 首先要普及一下中文字符的相關(guān)知識(shí)點(diǎn)
舉個(gè)例子,我們中文用的碼表,一般是GBK碼表(GBK碼表是java平臺(tái)默認(rèn)的中文編碼表,我們常用的UTF-8碼表中通常1個(gè)中文字符代表3個(gè)字節(jié))
- GBK碼表是1個(gè)中文分為2個(gè)字節(jié),第1個(gè)字節(jié)一定是個(gè)負(fù)數(shù),第2個(gè)字節(jié)是正數(shù)
- 計(jì)算機(jī)讀取的時(shí)候,雖然也是1個(gè)字節(jié)1個(gè)字節(jié)地讀取,但是它會(huì)讀到下一個(gè)負(fù)數(shù)字節(jié),停頓,然后兩個(gè)字節(jié)、兩個(gè)字節(jié)的拼接在一起讀取,這就是字符流讀取數(shù)據(jù)的原理。
- 別試圖去百度GBK碼表和UTF-8碼表的原理,光介紹的那一兩千字講暈?zāi)銘岩扇松挪恍拧?/li>
8.3 字符流的抽象父類:
Reader
Writer
我們從這里開始講起,其實(shí)正常的出招方式如下代碼:
FileReader fr = new FileReader("aaa.txt"); //創(chuàng)建輸入流對(duì)象,關(guān)聯(lián)aaa.txt
int ch;
while((ch = fr.read()) != -1) { //將讀到的字符賦值給ch
System.out.println((char)ch); //將讀到的字符強(qiáng)轉(zhuǎn)后打印
}
fr.close(); //關(guān)流
這里aaa.txt默認(rèn)創(chuàng)建就好,里面隨便寫些中文,別設(shè)置成utf-8編碼方式,小心亂碼懷疑人生。
寫出的出招方式如下:
FileWriter fw = new FileWriter("aaa.txt");
fw.write("大家好,我是渣渣輝。");
//97輸出后,在aaa.txt文件中會(huì)變成a,
//因?yàn)樽址飨忍幚碜址麊栴},再轉(zhuǎn)成字節(jié)流輸出,所以它同樣經(jīng)歷了字節(jié)流的處理
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();
一看就知道,還是熟悉的配方還是熟悉的套路。但是!
劃線重點(diǎn)來了。
8.31 原理,分析原理。
舉個(gè)例子,字符流的抽象父類 Reader,這個(gè)叫字符輸入流,因?yàn)槌橄?,所以不能用它來?shí)例化對(duì)象,我們來查看JDK API文檔:

可以看到,它繼承自O(shè)bject包,直接子類也不多。
- 在字節(jié)流里面,
InputStream類是爸爸,但我們可以看到,實(shí)際寫代碼時(shí)一般都是兒子FileInputStream在干活。
同樣字符流的爸爸Reader類,我們可以試著找一找有沒有個(gè)兒子叫FileReader類,這樣我們就可以照葫蘆畫瓢,這一小節(jié)也就不用學(xué)了,但是我們會(huì)發(fā)現(xiàn)——沒有!
我們找其中一個(gè)InputStreamReader類,試試看:

哎!你會(huì)發(fā)現(xiàn)它就只有一個(gè)直接子類
FileReader類,不著急,我們?cè)倏聪逻@個(gè)孫子FileReader類:

這可不是我截圖不全,已經(jīng)是全部了。會(huì)發(fā)現(xiàn),孫子地位不行,能力也沒有,全靠繼承老爹
InputStreamReader類(雖然它爹只生了一個(gè)兒子)和爺爺、太爺爺?shù)母鞣N方法。
我們平時(shí)要用的字符流,就是在用這個(gè)孫子FileReader類。
那為什么本來是兒子的地位淪落到孫子的地位,還這么慘?
這里涉及我們上面所說的裝飾者模式。(我們先不著急說明什么是裝飾者模式,暫且不表)
8.32 在寫出的時(shí)候,如下代碼:
FileWriter fw = new FileWriter("aaa.txt");
fw.write("大家好,我是渣渣輝。");
//97輸出后,在aaa.txt文件中會(huì)變成a,
//因?yàn)樽址飨忍幚碜址麊栴},再轉(zhuǎn)成字節(jié)流輸出,所以它同樣經(jīng)歷了字節(jié)流的處理
fw.write(97);
fw.close();
如果不寫fw.close(); ,執(zhí)行會(huì)發(fā)現(xiàn),什么也沒有存入。
哎不對(duì)呀!還學(xué)沒到緩沖字符流的地步,怎么不執(zhí)行fw.close(); 就不行了,FileWriter類 明顯不按套路出牌。
我們查看FileWriter 類源代碼:


這里就不貼代碼了,里面同樣看不出什么來,繼續(xù)看它的繼承父類OutputStreamWriter 的源代碼:

同樣看不出什么來,不急,繼續(xù)看它的繼承父類
Writer 的源代碼:
哎!這里有了,雖然不是緩沖流,但是它自帶了一個(gè)小緩沖
writeBuffer ,而且大小是1024,注意了,這里的1024不是1024個(gè)字節(jié),而是1024個(gè)字符 = 2048個(gè)字節(jié)。所以如果用字符流的
FileWriter類和FileReader類而不去關(guān)閉它們,就會(huì)丟失數(shù)據(jù)。
8.4 類比一下,同樣字符流也可以像字節(jié)流一樣,可以不必一個(gè)字符一個(gè)字符地讀和寫,自定義小數(shù)組:
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) { //將文件上的數(shù)據(jù)讀取到字 ···符數(shù)組中
fw.write(arr,0,len); //將字符數(shù)組中的數(shù)據(jù)寫到文件上
}
fr.close();
fw.close();
}
你看,字符流就這些東西,完畢。
9.0 緩沖字符流
9.1 緩沖字符流
同樣,目的還是為了提高效率,才會(huì)有緩沖字符流,如果你用字符流并不需要用到這樣的功效,那么大可不必使用緩沖字符流,用字符流就可以了。
-
BufferedReader的read()方法讀取字符時(shí)會(huì)一次讀取若干字符到緩沖區(qū), 然后逐個(gè)返回給程序, 降低讀取文件的次數(shù), 提高效率。 -
BufferedWriter的write()方法寫出字符時(shí)會(huì)先寫到緩沖區(qū), 緩沖區(qū)寫滿時(shí)才會(huì)寫到文件, 降低寫文件的次數(shù), 提高效率。
9.2 用法同樣很簡(jiǎn)單,反正都是3步走:
BufferedReader br = new BufferedReader(new FileReader("aaa.txt")); //創(chuàng)建字符輸入流對(duì)象,關(guān)聯(lián)aaa.txt
BufferedWriter bw = new BufferedWriter(new FileWriter("bbb.txt")); //創(chuàng)建字符輸出流對(duì)象,關(guān)聯(lián)bbb.txt
int ch;
//read一次,會(huì)先將緩沖區(qū)讀滿,從緩沖去中一個(gè)一個(gè)的返給臨時(shí)變量ch
while((ch = br.read()) != -1) {
//write一次,是將數(shù)據(jù)裝到字符數(shù)組,裝滿后再一起寫出去
bw.write(ch);
}
//關(guān)流
br.close();
bw.close();
其原理和字節(jié)流一模一樣。
9.3 細(xì)節(jié)來了。字符流中有一些方法,比較有意思,可以加以了解。
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();
}
- 關(guān)于這段代碼,我們可以去查看文檔,在我們的緩沖字符輸入流
BufferedReader中有一個(gè)readLine()方法,作用是讀取一個(gè)文本行。我們可以去查看這個(gè)方法的源碼,你就會(huì)發(fā)現(xiàn)自己居然也可以為java語言貢獻(xiàn)一個(gè)方法出來。
2019-03-10_003324.png
注意了,我們的緩沖字符輸入流BufferedReader中的readLine()方法,讀取后,相應(yīng)的換行、回車符(\r、\n)就丟了,如果你常試直接寫出的話,會(huì)發(fā)現(xiàn)所有的內(nèi)容都會(huì)在同一自然段全部寫完。
聰明的朋友已經(jīng)猜到了,沒錯(cuò)我們的緩沖字符輸出流BufferedWriter 中除了有一個(gè) write()方法,查看文檔還發(fā)現(xiàn)出現(xiàn)一個(gè)專門換行的方法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();
當(dāng)你需要這樣去讀取文件時(shí),上面就是可以copy的模板了。
細(xì)節(jié)又來了。我們的注釋語句里面有一句//bw.write("\r\n");,我們可以看到,newLine() 這個(gè)方法我們不可以直接通過注釋中那樣編寫代碼也能實(shí)現(xiàn)嗎?為什么java要這么累贅一下。
我們不要忘了,在windows操作環(huán)境下,他們的確是一樣的,但不同的是newLine() 方法可以兼容所有JVM運(yùn)行的平臺(tái)。
9.4 我們的緩沖字符輸入流BufferedReader ,還有一個(gè)獨(dú)生子。
-
LineNumberReader跟蹤行號(hào)的緩沖字符輸入流。繼承自BufferedReader父類。意思就是說,它和BufferedReader具有相同的功能, 一模一樣,只不過是多了個(gè)可以統(tǒng)計(jì)行號(hào)的技能。 -
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);注釋掉。
可以看到輸出會(huì)從第0行開始,加冒號(hào):,然后加具體哪一行的內(nèi)容,我們可以查看LineNumberReader 類的源代碼:


可以看到里面有一個(gè)變量
lineNumber 初始值是0,并提供了get( )方法 和set( )方法 。所以,當(dāng)我們把
lnr.setLineNumber(100);注釋去掉時(shí),會(huì)發(fā)現(xiàn)輸出語句的記錄行數(shù)會(huì)從第100行開始,就這么個(gè)功能,需要就用。而且這樣功能的類只有它有,其他緩沖流都沒有。
這一部分其實(shí)就這么寫內(nèi)容,完畢,更加深入的運(yùn)用,上面也給出一個(gè)另一篇文章的鏈接,里面會(huì)深入講解IO流的使用。粘貼下來:
【鏈接暫空】
10.0 裝飾者模式
當(dāng)然,我到現(xiàn)在依然分不明白23種設(shè)計(jì)模式,都不一定全能默寫出來,更別說分類了。
裝飾者模式通俗理解為 :小情人Mary過完輪到Sarah過生日,還是不要叫她自己挑了生日禮物,不然這個(gè)月伙食費(fèi)肯定玩完,拿出我去年在步行街照的照片,在背面寫上“最好的的禮物,就是愛你的老剛”,再到街上禮品店買了個(gè)像框(賣禮品的MM也很漂亮哦),再找隔壁搞美術(shù)設(shè)計(jì)的Mike設(shè)計(jì)了一個(gè)漂亮的盒子裝起來……,我們都是裝裱匠,最終都在裝飾我這個(gè)人。
開個(gè)玩笑。我們這里當(dāng)然是講解裝飾者模式,主要是基于IO流來說的,畢竟它基本上就是基于裝飾者模式弄出來的。舉個(gè)例子:
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; //獲取學(xué)生引用
//2,在構(gòu)造方法中傳入被裝飾類的對(duì)象
public JuanLanMenStudent (Student s) {
this.s = s;
}
//3,對(duì)原有的功能進(jìn)行升級(jí)
@Override
public void code() {
s.code();
System.out.println("ssh");
System.out.println("數(shù)據(jù)庫");
System.out.println("大數(shù)據(jù)");
System.out.println("...");
}
}
有一種編碼能力,剛出道的大學(xué)生,可能就學(xué)了點(diǎn)javase 和javaweb ,發(fā)現(xiàn)畢業(yè)后工作不好找,工資不咋地。于是決定拜山學(xué)藝,就拜入卷簾門當(dāng)學(xué)生。于是他在卷簾門還學(xué)了ssh 、數(shù)據(jù)庫 、大數(shù)據(jù) 、高速路發(fā)傳單等等等等 ,這樣對(duì)原來的大學(xué)生進(jìn)行裝飾,他就是卷簾門出品的大學(xué)生那畢業(yè)后就了不得了。
所以,重點(diǎn)又來了!——裝飾設(shè)計(jì)模式的好處是:
* 耦合性不強(qiáng),被裝飾的類的變化與裝飾類的變化無關(guān),子類的改變不會(huì)引起父類的改變。
11.0 下面講的東西怕看多了搞混,所以單拿出來寫最后,注意:這里面的功能都是字符流里面的特例了,和字節(jié)流沒有什么共同特性(字節(jié)流里面沒有類似的功能、方法)
11.1 使用指定的碼表讀寫字符
-
FileReader是使用默認(rèn)碼表讀取文件, 如果需要使用指定碼表讀取, 那么可以使用InputStreamReader(字節(jié)流,編碼表) -
FileWriter是使用默認(rèn)碼表寫出文件, 如果需要使用指定碼表寫出, 那么可以使用OutputStreamWriter(字節(jié)流,編碼表)
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();
**細(xì)節(jié):
- 1.0 ** 這里的
"UTF-8"、"GBK"里面的英文字母大小寫隨意,還可以大小寫亂搭,只要你字母順序別打錯(cuò)就行了。 - 2.0 這里發(fā)現(xiàn)沒,終于用了
InputStreamReader和OutputStreamWriter類,很多人有疑問,比如InputStreamReader類前半段是字節(jié)流的InputStream,后半段是字符流的Reader,那它到低算字節(jié)流還是字符流?
其實(shí)我們查看源代碼或者查看Java API文檔,就知道InputStreamReader類繼承自Reader類,當(dāng)然是根正苗紅的字符流了。 - 3.0 上面我們當(dāng)然還可以優(yōu)化,畢竟緩沖流的存在不就是為了這個(gè)么:
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
