Java IO的學(xué)習(xí)是一件非常艱巨的任務(wù)匈庭。
它的挑戰(zhàn)是來自于要覆蓋所有的可能性矮瘟。不僅存在各種I/O源端還有想要和他通信的接收端(文件/控制臺/網(wǎng)絡(luò)鏈接)借笙,而且還需要以不同的方式與他們進(jìn)行通信(順序/隨機(jī)存取/緩沖/二進(jìn)制/字符/行/字 等等)這些情況綜合起來就給我們帶來了大量的學(xué)習(xí)任務(wù)泻云,大量的類需要學(xué)習(xí)切平。
我們要學(xué)會所有的這些java 的IO是很難的,因?yàn)槲覀儧]有構(gòu)建一個關(guān)于IO的體系脆诉,要構(gòu)建這個體系又需要深入理解IO庫的演進(jìn)過程,所以贷币,我們?nèi)绻狈v史的眼光击胜,很快我們會對什么時候應(yīng)該使用IO中的哪些類,以及什么時候不該使用它們而困惑役纹。
所以偶摔,在開發(fā)者的眼中,IO很亂促脉,很多類辰斋,很多方法,很迷茫瘸味。
IO簡介
數(shù)據(jù)流是一組有序宫仗,有起點(diǎn)和終點(diǎn)的字節(jié)的數(shù)據(jù)序列。包括輸入流和輸出流旁仿。
流序列中的數(shù)據(jù)既可以是未經(jīng)加工的原始二進(jìn)制數(shù)據(jù)藕夫,也可以是經(jīng)一定編碼處理后符合某種格式規(guī)定的特定數(shù)據(jù)。因此Java中的流分為兩種: **1) 字節(jié)流:**數(shù)據(jù)流中最小的數(shù)據(jù)單元是字節(jié) **2) 字符流:**數(shù)據(jù)流中最小的數(shù)據(jù)單元是字符枯冈, Java中的字符是Unicode編碼毅贮,一個字符占用兩個字節(jié)。
Java.io包中最重要的就是5個類和一個接口尘奏。5個類指的是File滩褥、OutputStream、InputStream罪既、Writer铸题、Reader铡恕;一個接口指的是Serializable。掌握了這些就掌握了Java I/O的精髓了丢间。
Java I/O主要包括如下3層次:
- 流式部分——最主要的部分探熔。如:OutputStream、InputStream烘挫、Writer诀艰、Reader等
- 非流式部分——如:File類、RandomAccessFile類和FileDescriptor等類
-
其他——文件讀取部分的與安全相關(guān)的類饮六,如:SerializablePermission類其垄,以及與本地操作系統(tǒng)相關(guān)的文件系統(tǒng)的類,如:FileSystem類和Win32FileSystem類和WinNTFileSystem類卤橄。
IO詳細(xì)介紹
在Android 平臺绿满,從應(yīng)用的角度出發(fā),我們最需要關(guān)注和研究的就是 字節(jié)流(Stream)字符流(Reader/Writer)和 File/ RandomAccessFile窟扑。當(dāng)我們需要的時候再深入研究也未嘗不是一件好事喇颁。關(guān)于字符和字節(jié),例如文本文件嚎货,XML這些都是用字符流來讀取和寫入橘霎。而如RAR,EXE文件殖属,圖片等非文本姐叁,則用字節(jié)流來讀取和寫入。**面對如此復(fù)雜的類關(guān)系洗显,有一個點(diǎn)是我們必須要首先掌握的外潜,那就是設(shè)計(jì)模式中的修飾模式**,學(xué)會并理解修飾模式是搞懂流必備的前提條件哦墙懂。
字節(jié)流的學(xué)習(xí)
在具體的學(xué)習(xí)流之前橡卤,我們必須要學(xué)的一個設(shè)計(jì)模式是裝飾模式。因?yàn)閺牧鞯恼麄€發(fā)展歷史损搬,出現(xiàn)的各種類之間的關(guān)系看碧库,都是沿用了修飾模式,都是一個類的功能可以用來修飾其他類巧勤,然后組合成為一個比較復(fù)雜的流嵌灰。比如說:
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(file)));
從上面的代碼塊中大家不難看出這些類的關(guān)系:**為了向文件中寫入數(shù)據(jù),首先需要創(chuàng)建一個FileOutputStream颅悉,然后為了提升訪問的效率沽瞭,所以將它發(fā)送給具備緩存功能的BufferedOutput-Stream,而為了實(shí)現(xiàn)與機(jī)器類型無關(guān)的java基本類型數(shù)據(jù)的輸出,所以剩瓶,我們將緩存的流傳遞給了DataOutputStream**驹溃。從上面的關(guān)系城丧,我們可以看到,其根本目的都是為outputSteam添加額外的功能豌鹤。**而這種額外功能的添加就是采用了裝飾模式來構(gòu)建的代碼**亡哄。因此,學(xué)習(xí)流布疙,必須要學(xué)好裝飾模式蚊惯。
下面的圖是一個關(guān)于字節(jié)流的圖譜,這張圖譜比較全面的概況了我們字節(jié)流中間的各個類以及他們之間的關(guān)系灵临。
字節(jié)流的學(xué)習(xí)過程
為什么要按照一個學(xué)習(xí)路線來呢截型?原因是他們的功能決定的。
OutputStream -> FileOutputStream/FilterOutputStream ->DataOutputStream->bufferedOutputStream
相應(yīng)的學(xué)習(xí)InputStream方法就好了儒溉。
從學(xué)習(xí)的角度來宦焦,我們應(yīng)該先掌握FilterOutputStream, 以及FileOutputStream,這兩個類是基本的類顿涣,從繼承關(guān)系可以不難發(fā)現(xiàn)他們都是對 abstract 類 OutputStream的拓展赶诊,是它的子類。然而园骆,伴隨著 對 Stream流的功能的拓展,所以就出現(xiàn)了 DataOutputStream寓调,(將java中的基礎(chǔ)數(shù)據(jù)類型寫入數(shù)據(jù)字節(jié)輸出流中锌唾、保存在存儲介質(zhì)中、然后可以用DataOutputStream從存儲介質(zhì)中讀取到程序中還原成java基礎(chǔ)類型)夺英。這里多提一句晌涕、DataOutputStream、FilterOutputStream三個類的關(guān)系的這種設(shè)計(jì)既使用了裝飾器模式 避免了類的爆炸式增長痛悯。
為了提升Stream的執(zhí)行效率余黎,所以出現(xiàn)了bufferedOutputStream。bufferedOutputStream就是將本地添加了一個緩存的數(shù)組载萌。在使用bufferedOutputStream之前每次從磁盤讀入數(shù)據(jù)的時候都是需要訪問多少byte數(shù)據(jù)就向磁盤中讀多少個byte的數(shù)據(jù)惧财,而出現(xiàn)bufferedOutputSteam之后,策略就改了扭仁,會先讀取整個緩存空間相應(yīng)大小的數(shù)據(jù)垮衷,這樣就是從磁盤讀取了一塊比較大的數(shù)據(jù),然后緩存起來乖坠,從而減少了對磁盤的訪問的次數(shù)以達(dá)到提升性能的目的搀突。
另外一方面,我們知道了outputStream(輸出流)的發(fā)展歷史后熊泵,我們便可以知道如何使用outpuSteam了仰迁,同樣的方法甸昏,我們可以運(yùn)用到inputStream中來,這樣對稱的解釋就出現(xiàn)到了inputStream相關(guān)的中來了徐许,于是施蜜,我們對整個字節(jié)流就有了全方位的理解,所以這樣子我們就不會感覺到流的復(fù)雜了绊寻。這個時候?qū)τ谄渌囊恍┳止?jié)流的使用(byteArrayOutputStream/PipeOutputStream/ObjectOutputStream)的學(xué)習(xí)就自需要在使用的時候看看API即可花墩。
字符流的學(xué)習(xí)
下圖則是一個關(guān)于字符流的圖譜,這張圖譜比較全面的概況了我們字符流中間的各個類以及他們之間的關(guān)系澄步。
字符流的學(xué)習(xí)和字節(jié)流的學(xué)習(xí)是一樣的冰蘑,它和字節(jié)流有著同樣的發(fā)展過程,只是村缸,字節(jié)流面向的是我們未知或者即使知道了他們的編碼格式也意義不大的文件(png祠肥,exe, zip)的時候是采用字節(jié),而面對一些我們知道文件構(gòu)造我們就能夠搞懂它的意義的文件(json梯皿,xml)等文件的時候我們還是需要以字符的形式來讀取仇箱,所以就出現(xiàn)了字符流。reader 和 Stream最大的區(qū)別我認(rèn)為是它包含了一個readline()接口东羹,這個接口標(biāo)明了剂桥,一行數(shù)據(jù)的意義,這也是可以理解的属提,因?yàn)樽杂凶址啪邆湫械母拍钊ǘ海喾醋止?jié)流中的行也就是一個字節(jié)符號。
字符流的學(xué)習(xí)歷程:
Writer- >FilterWriter->BufferedWriter->OutputStreamWriter->FileWriter->其他
同時類比著學(xué)習(xí)Reader相關(guān)的類冤议。
FilterWriter/FilterReader
字符過濾輸出流斟薇、與FilterOutputStream功能一樣、只是簡單重寫了父類的方法恕酸、目的是為所有裝飾類提供標(biāo)準(zhǔn)和基本的方法堪滨、要求子類必須實(shí)現(xiàn)核心方法、和擁有自己的特色蕊温。這里FilterWriter沒有子類袱箱、可能其意義只是提供一個接口、留著以后的擴(kuò)展义矛。犯眠。。本身是一個抽象類症革。
BufferedWriter/BufferedReader
BufferedWriter是 Writer類的一個子類筐咧。他的功能是為傳入的底層字符輸出流提供緩存功能、同樣當(dāng)使用底層字符輸出流向目的地中寫入字符或者字符數(shù)組時、每寫入一次就要打開一次到目的地的連接量蕊、這樣頻繁的訪問不斷效率底下铺罢、也有可能會對存儲介質(zhì)造成一定的破壞、比如當(dāng)我們向磁盤中不斷的寫入字節(jié)時残炮、夸張一點(diǎn)韭赘、將一個非常大單位是G的字節(jié)數(shù)據(jù)寫入到磁盤的指定文件中的、沒寫入一個字節(jié)就要打開一次到這個磁盤的通道势就、這個結(jié)果無疑是恐怖的泉瞻、而當(dāng)我們使用BufferedWriter將底層字符輸出流、比如FileReader包裝一下之后苞冯、我們可以在程序中先將要寫入到文件中的字符寫入到BufferedWriter的內(nèi)置緩存空間中袖牙、然后當(dāng)達(dá)到一定數(shù)量時、一次性寫入FileReader流中舅锄、此時鞭达、FileReader就可以打開一次通道、將這個數(shù)據(jù)塊寫入到文件中皇忿、這樣做雖然不可能達(dá)到一次訪問就將所有數(shù)據(jù)寫入磁盤中的效果畴蹭、但也大大提高了效率和減少了磁盤的訪問量!
OutputStreamWriter/InputStreamReader
輸入字符轉(zhuǎn)換流鳍烁、是輸入字節(jié)流轉(zhuǎn)向輸入字符流的橋梁叨襟、用于將輸入字節(jié)流轉(zhuǎn)換成輸入字符流、通過指定的或者默認(rèn)的編碼將從底層讀取的字節(jié)轉(zhuǎn)換成字符返回到程序中幔荒、與OutputStreamWriter一樣芹啥、本質(zhì)也是使用其內(nèi)部的一個類來完成所有工作:StreamDecoder、使用默認(rèn)或者指定的編碼將字節(jié)轉(zhuǎn)換成字符铺峭;OutputStreamWriter/ InputStreamReader只是對StreamDecoder進(jìn)行了封裝、isr內(nèi)部所有方法核心都是調(diào)用StreamDecoder來完成的汽纠、InputStreamReader只是對StreamDecoder進(jìn)行了封裝卫键、使得我們可以直接使用讀取方法、而不用關(guān)心內(nèi)部實(shí)現(xiàn)虱朵。
OutputStreamWriter莉炉、InputStreamReader分別為InputStream、OutputStream的低級輸入輸出流提供將字節(jié)轉(zhuǎn)換成字符的橋梁碴犬、他們只是外邊的一個門面絮宁、真正的核心:
OutputStreamWriter中的StreamEncoder:
1、使用指定的或者默認(rèn)的編碼集將字符轉(zhuǎn)碼為字節(jié)
2服协、調(diào)用StreamEncoder自身實(shí)現(xiàn)的寫入方法將轉(zhuǎn)碼后的字節(jié)寫入到底層字節(jié)輸出流中绍昂。
InputStreamReader中的StreamDecoder:
1、使用指定的或者默認(rèn)的編碼集將字節(jié)解碼為字符
2、調(diào)用StreamDecoder自身實(shí)現(xiàn)的讀取方法將解碼后的字符讀取到程序中窘游。
在理解這兩個流的時候要注意:java——io中只有將字節(jié)轉(zhuǎn)換成字符的類唠椭、沒有將字符轉(zhuǎn)換成字節(jié)的類、原因很簡單——字符流的存在本來就像對字節(jié)流進(jìn)行了裝飾忍饰、加工處理以便更方便的去使用贪嫂、在使用這兩個流的時候要注意:由于這兩個流要頻繁的對讀取或者寫入的字節(jié)或者字符進(jìn)行轉(zhuǎn)碼、解碼和與底層流的源和目的地進(jìn)行交互艾蓝、所以使用的時候要使用BufferedWriter力崇、BufferedReader進(jìn)行包裝、以達(dá)到最高效率赢织、和保護(hù)存儲介質(zhì)亮靴。
FileReader/FileWriter
FileReader和FileWriter 繼承于InputStreamReader/OutputStreamWriter。
從源碼可以發(fā)現(xiàn)FileWriter 文件字符輸出流敌厘、主要用于將字符寫入到指定的打開的文件中台猴、其本質(zhì)是通過傳入的文件名、文件俱两、或者文件描述符來創(chuàng)建FileOutputStream饱狂、然后使用OutputStreamWriter使用默認(rèn)編碼將FileOutputStream轉(zhuǎn)換成Writer(這個Writer就是FileWriter)。如果使用這個類的話宪彩、最好使用BufferedWriter包裝一下休讳、高端大氣上檔次、低調(diào)奢華有內(nèi)涵尿孔!
FileReader 文件字符輸入流俊柔、用于將文件內(nèi)容以字符形式讀取出來、一般用于讀取字符形式的文件內(nèi)容活合、也可以讀取字節(jié)形式雏婶、但是因?yàn)镕ileReader內(nèi)部也是通過傳入的參數(shù)構(gòu)造InputStreamReader、并且只能使用默認(rèn)編碼白指、所以我們無法控制編碼問題留晚、這樣的話就很容易造成亂碼。所以讀取字節(jié)形式的文件還是使用字節(jié)流來操作的好告嘲、同樣在使用此流的時候用BufferedReader包裝一下错维、就算沖著BufferedReader的readLine()方法去的也要使用這個包裝類、不說他還能提高效率橄唬、保護(hù)存儲介質(zhì)赋焕。
字節(jié)流與字符流的關(guān)系
那么字節(jié)輸入流和字符輸入流之間的關(guān)系是怎樣的呢?請看下圖
同樣的字節(jié)與字符輸出流字節(jié)的關(guān)系也如下圖所示
字節(jié)流與字符流的區(qū)別
字節(jié)流和字符流使用是非常相似的仰楚,那么除了操作代碼的不同之外隆判,還有哪些不同呢犬庇?
字節(jié)流在操作的時候本身是不會用到緩沖區(qū)(內(nèi)存)的,是與文件本身直接操作的蜜氨,而字符流在操作的時候是使用到緩沖區(qū)的字節(jié)流在操作文件時械筛,即使不關(guān)閉資源(close方法),文件也能輸出飒炎,但是如果字符流不使用close方法的話埋哟,則不會輸出任何內(nèi)容,說明字符流用的是緩沖區(qū)郎汪,并且可以使用flush方法強(qiáng)制進(jìn)行刷新緩沖區(qū)赤赊,這時才能在不close的情況下輸出內(nèi)容
那開發(fā)中究竟用字節(jié)流好還是用字符流好呢?
在所有的硬盤上保存文件或進(jìn)行傳輸?shù)臅r候都是以字節(jié)的方法進(jìn)行的煞赢,包括圖片也是按字節(jié)完成抛计,而字符是只有在內(nèi)存中才會形成的,所以使用字節(jié)的操作是最多的照筑。
如果要java程序?qū)崿F(xiàn)一個拷貝功能吹截,應(yīng)該選用字節(jié)流進(jìn)行操作(可能拷貝的是圖片),并且采用邊讀邊寫的方式(節(jié)省內(nèi)存)凝危。
字節(jié)流與字符流的轉(zhuǎn)換
雖然Java支持字節(jié)流和字符流波俄,但有時需要在字節(jié)流和字符流兩者之間轉(zhuǎn)換。InputStreamReader和OutputStreamWriter蛾默,這兩個為類是字節(jié)流和字符流之間相互轉(zhuǎn)換的類懦铺。
InputSreamReader用于將一個字節(jié)流中的字節(jié)解碼成字符:
有兩個構(gòu)造方法:
InputStreamReader(InputStream in);
功能:用默認(rèn)字符集創(chuàng)建一個InputStreamReader對象
InputStreamReader(InputStream in,String CharsetName);
功能:接收已指定字符集名的字符串,并用該字符創(chuàng)建對象
OutputStream用于將寫入的字符編碼成字節(jié)后寫入一個字節(jié)流支鸡。
同樣有兩個構(gòu)造方法:
OutputStreamWriter(OutputStream out);
功能:用默認(rèn)字符集創(chuàng)建一個OutputStreamWriter對象冬念;
OutputStreamWriter(OutputStream out,String CharSetName);
功能:接收已指定字符集名的字符串,并用該字符集創(chuàng)建OutputStreamWrite對象
為了避免頻繁的轉(zhuǎn)換字節(jié)流和字符流牧挣,對以上兩個類進(jìn)行了封裝急前。
BufferedWriter類封裝了OutputStreamWriter類;
BufferedReader類封裝了InputStreamReader類瀑构;
封裝格式:
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(System.out));
BufferedReader in= new BufferedReader(new InputStreamReader(System.in);
利用下面的語句裆针,可以從控制臺讀取一行字符串:
BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
String line=in.readLine();
視頻觀看:
https://www.bilibili.com/video/BV1SQ4y1B7gN?spm_id_from=333.999.0.0