IO 流柳刮,掌控一切
上一篇文章我們認(rèn)識(shí)了文件操作的源頭 File 類挖垛,這篇文章就來(lái)聊聊文件操作的核心 IO 流。
我們經(jīng)潮牛可以聽(tīng)到:輸入流、輸出流送矩、字節(jié)流蚕甥、字符流、節(jié)點(diǎn)流栋荸、處理流等詞語(yǔ)菇怀,咋一聽(tīng),忍不住“哇~~~!”的一聲晌块,心里在想:“感覺(jué)好復(fù)雜的樣子爱沟,學(xué)習(xí) IO 流需要知道這么多東西啊匆背!”呼伸,從而有了畏難的情緒。大家千萬(wàn)不要被這些詞語(yǔ)嚇到钝尸,望而卻步括享,它們只不過(guò)是從三個(gè)維度對(duì) IO 流的總結(jié)。
學(xué)習(xí) IO 流是有套路的珍促,通過(guò)這篇文章的學(xué)習(xí)铃辖,你一定能掌握 IO 流的使用技巧,從而掌控一切文件操作問(wèn)題猪叙。
一娇斩、認(rèn)識(shí) IO 流
1仁卷、IO 流的分類
從 IO 流的流向來(lái)劃分,IO 流分為:輸入流犬第、輸出流五督。
從 IO 流要處理的數(shù)據(jù)來(lái)劃分,IO 流分為:字節(jié)流瓶殃、字符流充包。其中,字節(jié)流可以處理一切文件數(shù)據(jù)遥椿,包括純文本基矮,word文檔,pdf文檔冠场,圖片家浇,音頻和視頻等二進(jìn)制數(shù)據(jù);字符流只能處理純文本文件碴裙。
從 IO 流的功能來(lái)劃分钢悲,IO 流分為:節(jié)點(diǎn)流和處理流。其中舔株,節(jié)點(diǎn)流是用來(lái)包裝數(shù)據(jù)源(File)的莺琳,它直接和數(shù)據(jù)源連接,表示從一個(gè)節(jié)點(diǎn)讀取數(shù)據(jù)或者把數(shù)據(jù)寫(xiě)入到一個(gè)節(jié)點(diǎn)载慈;處理流是用來(lái)包裝節(jié)點(diǎn)流的惭等,它是對(duì)一個(gè)已經(jīng)存在的節(jié)點(diǎn)流進(jìn)行連接,處理流通過(guò)增加緩存的方式來(lái)提高輸入輸出操作的性能办铡。
總的來(lái)說(shuō)辞做,java.io 包中流的操作主要分為字節(jié)流和字符流兩類,他倆都有對(duì)應(yīng)的節(jié)點(diǎn)流與數(shù)據(jù)源進(jìn)行連接寡具,為了提高文件操作的性能秤茅,在節(jié)點(diǎn)流的基礎(chǔ)上提供了處理流,以便增強(qiáng)節(jié)點(diǎn)流的功能童叠,同時(shí)他倆都有輸入和輸出操作框喳。
通過(guò)上面的分類,大家先對(duì) IO 流先有一個(gè)初步的了解拯钻,后面結(jié)合代碼給大家進(jìn)一步講解帖努。
2、區(qū)分流的輸入與輸出
在程序中所有的數(shù)據(jù)都是以流的方式進(jìn)行傳輸?shù)姆喟悖绦蛐枰獢?shù)據(jù)的時(shí)候就用輸入流讀取數(shù)據(jù)拼余,當(dāng)程序需要將計(jì)算好的數(shù)據(jù)進(jìn)行保存到文件或者輸出到其他系統(tǒng)時(shí),就用輸出流寫(xiě)出數(shù)據(jù)亩歹。
簡(jiǎn)單來(lái)說(shuō)的話匙监,就是以我們的程序?yàn)橹行姆渤鳎绻峭獠康臄?shù)據(jù)流向程序,那么就是輸入流亭姥,輸入流一定是讀取操作稼钩;如果是程序里的數(shù)據(jù)流出到外部,那么就是輸出流达罗,輸出流一定是寫(xiě)出操作坝撑。
3、IO 操作的套路
Java 中 IO 操作也是有套路的粮揉,有標(biāo)準(zhǔn)的操作步驟巡李,主要的操作步驟如下:
1、使用 File 類與文件建立聯(lián)系
2扶认、選擇對(duì)應(yīng)的輸入流或者輸出流
3侨拦、進(jìn)行讀或?qū)懖僮?/p>
4、關(guān)閉資源
先對(duì)這個(gè)套路進(jìn)行一個(gè)了解辐宾,后面結(jié)合代碼一下就明白了狱从,原來(lái)套路如此簡(jiǎn)單。
二叠纹、萬(wàn)能鑰匙字節(jié)流
1季研、認(rèn)識(shí)字節(jié)流
字節(jié)流主要操作 byte 類型數(shù)據(jù),說(shuō)它是萬(wàn)能鑰匙吊洼,是因?yàn)樗梢蕴幚硪磺形募得玻ㄎ谋尽ord文檔冒窍、Excel文檔、pdf文檔豺鼻、圖片综液、語(yǔ)音、視頻等儒飒,統(tǒng)統(tǒng)都可以處理谬莹。
字節(jié)流分為字節(jié)輸入流和字節(jié)輸出流,在 Java 中 字節(jié)輸入流用 InputStream 表示桩了,字節(jié)輸出流用 OutputStream 表示附帽。
字節(jié)輸入流:InputStream 是一個(gè)抽象類,必須依靠其子類 FileInputStream 來(lái)讀取文件內(nèi)容井誉,輸入到程序中蕉扮。我們常用的方法是:
int read(byte b[]) //讀取byte數(shù)組中的內(nèi)容,返回讀入的長(zhǎng)度
close() //關(guān)閉資源
字節(jié)輸出流:OutputStream 是一個(gè)抽象類颗圣,必須依靠其子類 FileOutputStream 來(lái)讀取文件內(nèi)容喳钟,輸入到程序中屁使。我們常用的方法是:
//將一個(gè)制定范圍的byte數(shù)組輸出
void write(byte b[], int off, int len)
close() //關(guān)閉資源
flush() // 在關(guān)閉資源的時(shí)候默認(rèn)會(huì)調(diào)用刷新方法
2、字節(jié)輸出流 FileOutputStream 的使用
我們來(lái)看一個(gè)例子奔则,把“演示字節(jié)輸出流的使用\r\n用 FileOutputStream 類操作蛮寂!”的文本輸出到 D:/file/txt/output.txt 文件中。
因?yàn)槲募僮饔锌赡馨l(fā)生 FileNotFoundException 和 IOException易茬,為了精簡(jiǎn)代碼酬蹋,便于閱讀主要代碼,除了本例子以外抽莱,后續(xù)的例子我會(huì)直接使用 throws 關(guān)鍵字拋出異常范抓,并且關(guān)閉資源也不放在finally里,這樣可以減少 try...catch...finally的代碼岸蜗。
@Test
public void testOutput() {
// 1尉咕、建立聯(lián)系, File對(duì)象璃岳, 輸出文件的地址
// 如果文件不存在則可以創(chuàng)建文件并寫(xiě)入年缎,
// 但是如果加了文件夾,那么文件夾不存在則會(huì)產(chǎn)生FileNotFoundException铃慷,系統(tǒng)找不到指定的路徑
String path = "D:/txt/output.txt";
File file = new File(path);
// 2单芜、選擇流
// 由于os要在finally中用到,放到try的外部犁柜,以提升os的變量作用范圍
OutputStream os = null;
try {
// 用FileOutputStream子類實(shí)例化父類OutputStream
// 以追加的方式輸出到文件洲鸠,必須是true,否則就會(huì)覆蓋原有的文件
os = new FileOutputStream(file, true);
// 3馋缅、操作
String info = "演示字節(jié)輸出流的使用\r\n用 FileOutputStream 類操作扒腕!\r\n";
byte[] b = info.getBytes();// 字符串轉(zhuǎn)字節(jié)數(shù)組
os.write(b, 0, b.length);// 寫(xiě)出
// 要養(yǎng)成這個(gè)習(xí)慣,為了避免緩存沒(méi)有寫(xiě)出去,需要顯示地flush一下
os.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("文件不存在");
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件寫(xiě)出失敗");
} finally {
try {
// 4萤悴、釋放資源
if (os != null) {
os.close();
}
} catch (Exception e2) {
System.out.println("關(guān)閉文件輸出流資源失敗");
}
}
}
運(yùn)行結(jié)果:
3瘾腰、字節(jié)輸入流 FileInputStream 的使用
上面的例子我們學(xué)會(huì)了字節(jié)輸出流的使用,下面用字節(jié)輸入流 FileInputStream 來(lái)讀取上面的文件內(nèi)容覆履。
@Test
public void testInput() throws IOException {
// 1蹋盆、建立聯(lián)系
File file = new File("D:/output.txt");
// 2、選擇流
InputStream is = new FileInputStream(file);
// 3硝全、讀操作:即不斷地讀取
byte[] b = new byte[1024]; // 緩存數(shù)組
int len = 0; // 接收實(shí)際讀取的大小
while ((len = is.read(b)) != -1) {
// 能讀取到數(shù)據(jù)則輸出栖雾,字節(jié)數(shù)組轉(zhuǎn)成字符串
String info = new String(b, 0, len);
System.out.println(info);
}
is.close();
}
運(yùn)行結(jié)果:
演示字節(jié)輸出流的使用
用 FileOutputStream 類操作!
演示字節(jié)輸出流的使用
用 FileOutputStream 類操作伟众!
4析藕、使用字節(jié)流,完成圖片文件的拷貝
下面的例子演示如何通過(guò)字節(jié)流對(duì)圖片文件進(jìn)行拷貝操作赂鲤,假設(shè)把 tomcat.png 拷貝成 tomcat1.jpg噪径。
文件的拷貝操作的思路就是柱恤,用字節(jié)輸入流讀取圖片 tomcat.png 的內(nèi)容,用字節(jié)輸出流寫(xiě)出到 tomcat1.jpg 文件中找爱,根據(jù)文件操作的套路梗顺,很容易就能寫(xiě)出以下的代碼:
@Test
public void testCopy() throws IOException {
// 1、使用File類與文件建立聯(lián)系
File srcFile = new File("D:/file/image/tomcat.png");
File destFile = new File("D:/file/image/tomcat1.jpg");
// 2车摄、選擇對(duì)應(yīng)的輸入流或者輸出流
InputStream is = new FileInputStream(srcFile);
OutputStream os = new FileOutputStream(destFile);
// 3寺谤、進(jìn)行讀或?qū)懖僮? byte[] b = new byte[1024];
int len = 0;
while ((len = is.read(b)) != -1) {
// 判斷每次讀取的內(nèi)容長(zhǎng)度,如果不等于-1吮播,表示文件沒(méi)有讀完
// 選擇帶參數(shù)的write方法变屁,就是為了避免byte緩存比實(shí)際內(nèi)容多的時(shí)候,輸出多余的空內(nèi)容
os.write(b, 0, len);
}
os.flush();
// 4意狠、關(guān)閉資源粟关,先創(chuàng)建的后關(guān)閉
os.close();
is.close();
}
運(yùn)行結(jié)果:
三环戈、純文本操作字符流
1闷板、認(rèn)識(shí)字符流
字符流主要操作純文本類型數(shù)據(jù),只能處理 txt院塞、html 等文本類型的數(shù)據(jù)遮晚,在程序中一個(gè)字符等于兩個(gè)字節(jié),Java 提供了 Reader 類和 Writer 類用于專門(mén)操作字符流拦止。
字符流也分為字符輸入流和字符輸出流县遣,在 Java 中 字符輸入流用 Reader 表示,輸出流用 Writer 表示汹族。
字符輸入流:Reader 是一個(gè)抽象類萧求,必須依靠其子類 FileReader 來(lái)讀取純文本文件內(nèi)容,輸入到程序中顶瞒。我們常用的方法是:
int read(char cbuf[]) //讀取char數(shù)組中的內(nèi)容饭聚,返回讀入的長(zhǎng)度
close() //關(guān)閉資源
字符輸出流:Writer 是一個(gè)抽象類,必須依靠其子類 FileWriter 來(lái)讀取純文本文件內(nèi)容搁拙,輸入到程序中。我們常用的方法是:
//將一個(gè)字符串輸出
void write(String str)
//將一個(gè)字符數(shù)組輸出
void write(char cbuf[], int off, int len)
close() //關(guān)閉資源
flush() // 在關(guān)閉資源的時(shí)候默認(rèn)會(huì)調(diào)用刷新方法
2法绵、字符輸出流 FileWriter 的使用
我們來(lái)看一個(gè)例子箕速,把“演示字符輸出流的使用\r\n用 FileWriter 類操作!”的文本輸出到 D:/file/txt/output_char.txt 文件中朋譬。
@Test
public void testWriter() throws IOException {
// 1盐茎、使用File類與文件建立聯(lián)系
File file = new File("D:/file/txt/output_char.txt");
// 2、選擇對(duì)應(yīng)的輸入流或者輸出流
Writer writer = new FileWriter(file, true);
String info = "演示字符輸出流的使用\r\n用 FileWriter 類操作徙赢!\r\n";
// 3字柠、進(jìn)行寫(xiě)操作
writer.write(info); //將一個(gè)字符串組輸出
writer.flush();
// 4探越、關(guān)閉資源
writer.close();
}
運(yùn)行結(jié)果:
3、字符輸入流 FileReader 的使用
上面的例子我們學(xué)會(huì)了字符輸出流的使用窑业,下面用字符輸入流 FileReader 來(lái)讀取上面的文件內(nèi)容钦幔。
@Test
public void testReader() throws IOException {
// 1、使用File類與文件建立聯(lián)系
File file = new File("D:/file/txt/output_char.txt");
// 2常柄、選擇對(duì)應(yīng)的輸入流或者輸出流
Reader reader = new FileReader(file);
char[] cbuf = new char[1024];
int len = 0;
// 3鲤氢、進(jìn)行寫(xiě)操作
while ((len = reader.read(cbuf)) != -1) {
String info = new String(cbuf, 0, len); // 字符數(shù)組轉(zhuǎn)成字符串
System.out.println(info);
}
// 4、關(guān)閉資源
reader.close();
}
運(yùn)行結(jié)果:
演示字符輸出流的使用
用 FileWriter 類操作西潘!
演示字符輸出流的使用
用 FileWriter 類操作卷玉!
4、利用字符流喷市,完成 txt文本文件的拷貝
下面的例子演示如何通過(guò)字符流對(duì)圖片文件進(jìn)行拷貝操作相种,把 output_char.txt 拷貝成 output_char1.txt。
@Test
public void testTxtCopy() throws IOException {
// 1品姓、使用File類與文件建立聯(lián)系
File srcFile = new File("D:/file/txt/output_char.txt");
File destFile = new File("D:/file/txt/output_char1.txt");
// 2寝并、選擇對(duì)應(yīng)的輸入流或者輸出流
Reader read = new FileReader(srcFile);
Writer write = new FileWriter(destFile);
// 3、進(jìn)行讀寫(xiě)操作
char[] cbuf = new char[1024];
int len = 0;
while ((len = read.read(cbuf)) != -1) {
write.write(cbuf, 0, len); //將一個(gè)字符數(shù)組輸出
}
write.flush();
// 4缭黔、關(guān)閉資源
write.close();
read.close();
}
運(yùn)行結(jié)果:
四、字節(jié)流與字符流的區(qū)別
1馏谨、字符輸出流在寫(xiě)出文件時(shí)用到了緩存區(qū)
除去剛才講過(guò)的别渔,字節(jié)流可以處理一切文件,字符流只能處理純文本文件惧互,兩者還有一個(gè)明顯的差異哎媚,那就是字符輸出流在操作文件時(shí)使用了緩沖區(qū),通過(guò)緩沖區(qū)再寫(xiě)出到文件喊儡,而字節(jié)輸出流直接操作文件拨与。
1、通過(guò)源碼可以證明字符輸出流用到了緩存區(qū)
2艾猜、通過(guò)兩段代碼的輸出結(jié)果證明字符輸出流用到了緩存區(qū)
- 驗(yàn)證字符流:
/**
* 把flush方法和close方法去掉买喧,觀察程序運(yùn)行結(jié)果,用字符流輸出內(nèi)容到文件是空的
*/
@Test
public void testWriter1() throws IOException {
// 1匆赃、使用File類與文件建立聯(lián)系
File file = new File("D:/file/txt/output_char_buffer.txt");
// 2淤毛、選擇對(duì)應(yīng)的輸入流或者輸出流
Writer writer = new FileWriter(file, true);
String info = "把flush方法和close方法去掉,觀察程序運(yùn)行結(jié)果算柳,輸出的內(nèi)容文件是空的低淡!\r\n";
// 3、進(jìn)行寫(xiě)操作
writer.write(info);
}
運(yùn)行結(jié)果:
- 驗(yàn)證字節(jié)流:
/**
* 把flush方法和close方法去掉,觀察程序運(yùn)行結(jié)果蔗蹋,用字節(jié)流可以輸出內(nèi)容到文件
*/
@Test
public void testOutput1() throws IOException {
// 1何荚、使用File類與文件建立聯(lián)系
File file = new File("D:/file/txt/output_char_output.txt");
// 2、選擇對(duì)應(yīng)的輸入流或者輸出流
OutputStream os = new FileOutputStream(file, true);
// 3猪杭、進(jìn)行寫(xiě)操作
String info = "把flush方法和close方法去掉餐塘,觀察程序運(yùn)行結(jié)果,輸出的內(nèi)容文件是空的胁孙!\r\n";
byte[] b = info.getBytes();// 字符串轉(zhuǎn)字節(jié)數(shù)組
os.write(b, 0, b.length);// 寫(xiě)出
}
運(yùn)行結(jié)果:
通過(guò)以上的 2 段程序唠倦,可以看出,字符流是有緩存的涮较,如果我們沒(méi)有調(diào)用 flush 方法稠鼻,并且沒(méi)有調(diào)用 close 方法,是無(wú)法把內(nèi)容寫(xiě)到文件中的狂票。但是同樣的沒(méi)有調(diào)用 flush 方法和 close 方法候齿,字節(jié)流確可以把內(nèi)容寫(xiě)出到文件。
- 驗(yàn)證字符流調(diào)用 flush方法闺属,不調(diào)用 close 方法的結(jié)果
/**
* 調(diào)用flush方法慌盯,不調(diào)用close方法,觀察程序運(yùn)行結(jié)果掂器,用字符流輸出內(nèi)容到文件是可以的亚皂,說(shuō)明字符輸出流確實(shí)用到了緩沖區(qū)
*/
@Test
public void testWriter2() throws IOException {
// 1、使用File類與文件建立聯(lián)系
File file = new File("D:/file/txt/output_char_writer.txt");
// 2国瓮、選擇對(duì)應(yīng)的輸入流或者輸出流
Writer writer = new FileWriter(file);
String info = "調(diào)用flush方法灭必,不調(diào)用close方法,觀察程序運(yùn)行結(jié)果乃摹,用字符流輸出內(nèi)容到文件是可以的禁漓,說(shuō)明字符輸出流確實(shí)用到了緩沖區(qū)!\r\n";
// 3孵睬、進(jìn)行寫(xiě)操作
writer.write(info);
// 4播歼、強(qiáng)制刷出
writer.flush();
}
運(yùn)行結(jié)果:
- 驗(yàn)證字符流調(diào)用 close 方法掰读,不調(diào)用 flush 方法的結(jié)果
/**
* 調(diào)用close方法秘狞,不調(diào)用flush方法,觀察程序運(yùn)行結(jié)果蹈集,用字符流輸出內(nèi)容到文件是可以的谒撼,說(shuō)明字符輸出流確實(shí)用到了緩沖區(qū)
*/
@Test
public void testWriter3() throws IOException {
// 1、使用File類與文件建立聯(lián)系
File file = new File("D:/file/txt/output_char_writer.txt");
// 2雾狈、選擇對(duì)應(yīng)的輸入流或者輸出流
Writer writer = new FileWriter(file);
String info = "調(diào)用close方法,不調(diào)用flush方法抵皱,觀察程序運(yùn)行結(jié)果善榛,用字符流輸出內(nèi)容到文件是可以的辩蛋,說(shuō)明字符輸出流確實(shí)用到了緩沖區(qū)!\r\n";
// 3移盆、進(jìn)行寫(xiě)操作
writer.write(info);
// 4悼院、關(guān)閉資源
writer.close();
}
運(yùn)行結(jié)果:
通過(guò)以上的 2 段程序咒循,可以看出据途,字符流是有緩存的,通過(guò)顯示調(diào)用 flush 方法可以把緩存內(nèi)容輸出到文件叙甸,如果沒(méi)有調(diào)用 flush 方法颖医,在調(diào)用 close 方法時(shí),默認(rèn)也是會(huì)把緩存內(nèi)容輸出到文件裆蒸。
切記字符輸出流在flush方法和close方法都沒(méi)有調(diào)用的時(shí)候熔萧,是無(wú)法輸出內(nèi)容到文件的。為了避免出現(xiàn)此類問(wèn)題僚祷,我們?cè)谑褂幂敵隽鞯臅r(shí)候佛致,不管是字節(jié)流還是字符流最好都顯示的調(diào)用一下 flush 方法。
講了這么多辙谜,大家覺(jué)得我們?cè)诓僮魑募臅r(shí)候是用字節(jié)流好呢還是用字符流好呢俺榆,答案是使用字節(jié)流更好,因?yàn)樗械奈募诖疟P(pán)中以及網(wǎng)絡(luò)傳輸都是以二進(jìn)制的字節(jié)傳輸?shù)淖岸撸?strong>在實(shí)際開(kāi)發(fā)中罐脊,字節(jié)流用的比較廣泛。
我們?cè)賮?lái)明確一下烂琴,文件操作的套路只有4步:
1爹殊、使用File類與文件建立聯(lián)系
2、選擇對(duì)應(yīng)的輸入流或者輸出流
3奸绷、進(jìn)行讀或?qū)懖僮?br>
4梗夸、關(guān)閉資源
另外讀寫(xiě)操作也是有固定套路的:
byte[] b = new byte[1024];
int len = 0;
while ((len = is.read(b)) != -1) {
os.write(b, 0, len);
}