I/O類庫中使用“流”這個抽象概念。Java對設(shè)備中數(shù)據(jù)的操作是通過流的方式。表示任何有能力產(chǎn)出數(shù)據(jù)的數(shù)據(jù)源對象,或者是有能力接受數(shù)據(jù)的接收端對象纯衍。“流”屏蔽了實(shí)際的I/O設(shè)備中處理數(shù)據(jù)的細(xì)節(jié)苗胀。IO流用來處理設(shè)備之間的數(shù)據(jù)傳輸襟诸。設(shè)備是指硬盤、內(nèi)存基协、鍵盤錄入歌亲、網(wǎng)絡(luò)等。
IO的分類可以為:
- 流按操作數(shù)據(jù)類型的不同分為兩種:字節(jié)流與字符流澜驮。
-
流按流向分為:輸入流陷揪,輸出流(以程序?yàn)閰⒄瘴铮斎氲匠绦蛟忧睿蚴菑某绦蜉敵觯?/p>
一悍缠、字節(jié)流
1、Inpustream
InputStream
有read
方法耐量,一次讀取一個字節(jié)飞蚓,OutputStream
的write
方法一次寫一個int
。這兩個類都是抽象類廊蜒。意味著不能創(chuàng)建對象趴拧,那么需要找到具體的子類來使用。操作流的步驟都是:
第一步:1:打開流(即創(chuàng)建流)
第二步:2:通過流讀取內(nèi)容
第三步:3:用完后山叮,關(guān)閉流資源
案例一:使用 read()
方法著榴,一次讀取一個字節(jié),讀到文件末尾返回-1.
private static void showContent(String path) throws IOException {
// 打開流
FileInputStream fis = new FileInputStream(path);
int len;
while ((len = fis.read()) != -1) {
System.out.print((char) len);
}
// 使用完關(guān)閉流
fis.close();
案例二:使用read()
方法的時候,可以將讀到的數(shù)據(jù)裝入到字節(jié)數(shù)組中屁倔,一次性的操作數(shù)組脑又,可以提高效率。
private static void showContent2(String path) throws IOException {
// 打開流
FileInputStream fis = new FileInputStream(path);
// 通過流讀取內(nèi)容
byte[] byt = new byte[1024];
int len = fis.read(byt);
for (int i = 0; i <len; i++) {
System.out.print(byt[i]);
}
// 使用完關(guān)閉流
fis.close();
}
案例三:使用read(byte[] b,int off,int len)
/**
* 把數(shù)組的一部分當(dāng)做流的容器來使用
* read(byte[] b,int off,int len)
*/
private static void showContent3(String path) throws IOException {
// 打開流
FileInputStream fis = new FileInputStream(path);
// 通過流讀取內(nèi)容
byte[] byt = new byte[1024];
// 從什么地方開始存讀到的數(shù)據(jù)
int start = 5;
// 希望最多讀多少個(如果是流的末尾锐借,流中沒有足夠數(shù)據(jù))
int maxLen = 6;
// 實(shí)際存放了多少個
int len = fis.read(byt, start, maxLen);
for (int i = start; i < start + maxLen; i++) {
System.out.print((char) byt[i]);
}
// 使用完關(guān)閉流
fis.close();
}
案例四(推薦使用):使用緩沖(提高效率),并循環(huán)讀取(讀完所有內(nèi)容).
/**
* 使用字節(jié)數(shù)組當(dāng)緩沖
* */
private static void showContent7(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
byte[] byt = new byte[1024];
int len = 0;
while ((len = fis.read(byt)) != -1) {
System.out.println(new String(byt, 0, len));
}
fis.close();
}
2问麸、OutputStream
OutputStram
的write
方法,一次只能寫一個字節(jié)瞎饲。成功的向文件中寫入了內(nèi)容口叙。但是并不高效,如何提高效率呢嗅战?可以使用緩沖妄田,在OutputStram
類中有write(byte[] b)
方法,將 b.length
個字節(jié)從指定的 byte
數(shù)組寫入此輸出流中驮捍。
private static void writeTxtFile(String path) throws IOException {
// 1:打開文件輸出流疟呐,流的目的地是指定的文件
FileOutputStream fos = new FileOutputStream(path,true);
// 2:通過流向文件寫數(shù)據(jù)
byte[] byt = "java".getBytes();
fos.write(byt);
// 3:用完流后關(guān)閉流
fos.close();
}
3、輸入輸出流綜合使用——文件拷貝實(shí)現(xiàn)
public static void copyFile(String srcPath, String destPath) throws IOException {
// 打開輸入流东且,輸出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
// 讀取和寫入信息
int len = 0;
// 使用字節(jié)數(shù)組启具,當(dāng)做緩沖區(qū)
byte[] byt = new byte[1024];
while ((len = fis.read(byt)) != -1) {
fos.write(byt, 0, len);
}
// 關(guān)閉流
fis.close();
fos.close();
}
可以根據(jù)拷貝的需求調(diào)整數(shù)組的大小,一般是1024的整數(shù)倍珊泳。使用緩沖后效率大大提高鲁冯。目前我們是拋出處理拷沸,一旦出現(xiàn)了異常,close
就沒有執(zhí)行薯演,也就沒有釋放資源撞芍。那么為了保證close
的執(zhí)行該如何處理呢。那么就需要使用try{} catch(){}finally{}
語句跨扮。try
中放入可能出現(xiàn)異常的語句序无,catch
是捕獲異常對象,fianlly
是一定要執(zhí)行的代碼衡创。
public static void copyFile(String srcPath, String destPath) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcPath);
fos = new FileOutputStream(destPath);
byte[] byt = new byte[1024 * 1024];
int len = 0;
while ((len = fis.read(byt)) != -1) {
fos.write(byt, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
大量的異常捕獲代碼使得以上代碼變得十分臃腫難看帝嗡,好在java7提供了TWR(try-with-resource)語法糖,由于很多外部資源類都間接的實(shí)現(xiàn)了AutoCloseable
接口(單方法回調(diào)接口)璃氢,因此可以利用TWR語法在try
結(jié)束的時候通過回調(diào)的方式自動調(diào)用外部資源類的close()
方法哟玷,避免書寫冗長的finally
代碼塊。`
public static void copyByTWR(String srcPath, String destPath){
try( FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath)){
byte[] byt = new byte[1024 * 1024];
int len = 0;
while ((len = fis.read(byt)) != -1) {
fos.write(byt, 0, len);
}
}catch (IOException e){
throw new RuntimeException(e);
}
}
當(dāng)try
語句塊運(yùn)行結(jié)束時拔莱,FileInputStream / FileOutputStream
會被自動關(guān)閉碗降。這是因?yàn)?code>FileInputStream實(shí)現(xiàn)了java中的java.lang.AutoCloseable
接口。所有實(shí)現(xiàn)了這個接口的類都可以在try-with-resources
結(jié)構(gòu)中使用上面的例子在try關(guān)鍵字后的括號里創(chuàng)建了兩個資源——FileInputStream
和FileOutputStream
塘秦。當(dāng)程序運(yùn)行離開try語句塊時讼渊,這兩個資源都會被自動關(guān)閉,這些資源將按照他們被創(chuàng)建順序的逆序來關(guān)閉。首先FileOutputStream
會被關(guān)閉尊剔,然后FileInputStream
會被關(guān)閉爪幻。
怎么實(shí)現(xiàn)對資源的關(guān)閉呢?當(dāng)try-with-resources
結(jié)構(gòu)中拋出一個異常须误,同時FileInputStream
被關(guān)閉時(調(diào)用了其close
方法)也拋出一個異常挨稿,try-with-resources
結(jié)構(gòu)中拋出的異常會向外傳播,而FileInputStream
被關(guān)閉時拋出的異常被抑制了京痢。這與文章開始處利用舊風(fēng)格代碼的例子(在finally語句塊中關(guān)閉資源)相反奶甘。
4、緩沖流
Java其實(shí)提供了專門的字節(jié)流緩沖來提高效率祭椰。BufferedInputStream
和 BufferedOutputStream臭家。BufferedOutputStream
和BufferedOutputStream
類可以通過減少讀寫次數(shù)來提高輸入和輸出的速度。它們內(nèi)部有一個緩沖區(qū)方淤,用來提高處理效率钉赁。查看API文檔,發(fā)現(xiàn)可以指定緩沖區(qū)的大小携茂。其實(shí)內(nèi)部也是封裝了字節(jié)數(shù)組你踩。沒有指定緩沖區(qū)大小,默認(rèn)的字節(jié)是8192。顯然緩沖區(qū)輸入流和緩沖區(qū)輸出流要配合使用带膜。首先緩沖區(qū)輸入流會將讀取到的數(shù)據(jù)讀入緩沖區(qū)吩谦,當(dāng)緩沖區(qū)滿時,或者調(diào)用flush
方法膝藕,緩沖輸出流會將數(shù)據(jù)寫出逮京。
注意:當(dāng)然使用緩沖流來進(jìn)行提高效率時,對于小文件可能看不到性能的提升束莫。但是文件稍微大一些的話,就可以看到實(shí)質(zhì)的性能提升了草描。
public class Test {
public static void main(String[] args) throws IOException {
String srcPath = "c:\\a.mp3";
String destPath = "d:\\copy.mp3";
copyFile(srcPath, destPath);
}
public static void copyFile(String srcPath, String destPath)
throws IOException {
// 打開輸入流览绿,輸出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
// 使用緩沖流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 讀取和寫入信息
int len = 0;
while ((len = bis.read()) != -1) {
bos.write(len);
}
// 關(guān)閉流
bis.close();
bos.close();
}
}
二、字符流
計算機(jī)并不區(qū)分二進(jìn)制文件與文本文件穗慕。所有的文件都是以二進(jìn)制形式來存儲的饿敲,因此,從本質(zhì)上說逛绵,所有的文件都是二進(jìn)制文件怀各。所以字符流是建立在字節(jié)流之上的,它能夠提供字符層次的編碼和解碼术浪∑岸裕可以說字符流就是:字節(jié)流 + 編碼表,為了更便于操作文字?jǐn)?shù)據(jù)胰苏。字符流的抽象基類:Reader 硕蛹, Writer
。由這些類派生出來的子類名稱都是以其父類名作為子類名的后綴硕并,如FileReader法焰、FileWriter
。
1倔毙、Reader
int read()
讀取一個字符埃仪。返回的是讀到的那個字符。如果讀到流的末尾陕赃,返回-1.
int read(char[])
將讀到的字符存入指定的數(shù)組中卵蛉,返回的是讀到的字符個數(shù),也就是往數(shù)組里裝的元素的個數(shù)凯正。如果讀到流的末尾毙玻,返回-1.
close()
讀取字符其實(shí)用的是window系統(tǒng)的功能,就希望使用完畢后廊散,進(jìn)行資源的釋放
由于Reader
也是抽象類桑滩,所以想要使用字符輸入流需要使用Reader
的實(shí)現(xiàn)類——FileReader
。1,用于讀取文本文件的流對象运准。2幌氮,用于關(guān)聯(lián)文本文件。
構(gòu)造函數(shù):在讀取流對象初始化的時候胁澳,必須要指定一個被讀取的文件该互。如果該文件不存在會發(fā)生FileNotFoundException。
/**
* 使用字符流讀取文件內(nèi)容
*/
public static void readFileByReader(String path) throws Exception {
Reader reader = new FileReader(path);
int len = 0;
while ((len = reader.read()) != -1) {
System.out.print((char) len);
}
reader.close();
}
2韭畸、Writer
write(ch): //將一個字符寫入到流中宇智。
write(char[]):// 將一個字符數(shù)組寫入到流中。
write(String): //將一個字符串寫入到流中胰丁。
flush(): //刷新流随橘,將流中的數(shù)據(jù)刷新到目的地中,流還存在锦庸。
close(): //關(guān)閉資源:在關(guān)閉前會先調(diào)用flush()机蔗,刷新流中的數(shù)據(jù)去目的地。然流關(guān)閉甘萧。
基本方法和OutputStream
類似萝嘁,有write
方法,功能更多一些扬卷⊙姥裕可以接收字符串。Writer
是抽象類無法創(chuàng)建對象邀泉,Writer
的實(shí)現(xiàn)子類為FileWriter
嬉挡。默認(rèn)的FileWriter
方法新值會覆蓋舊值,想要實(shí)現(xiàn)追加功能需要使用如下構(gòu)造函數(shù)創(chuàng)建輸出流 append
值為true
了汇恤。
FileWriter(String fileName, boolean append)
FileWriter(File file, boolean append)
3庞钢、文件拷貝——完善異常處理風(fēng)格
一次讀一個字符就寫一個字符,效率不高因谎。把讀到的字符放到字符數(shù)組中基括,再一次性的寫出,緩沖數(shù)組可以提高效率财岔。
/**
* 使用字符流拷貝文件风皿,有完善的異常處理
*/
public static void copyFile2(String path1, String path2) {
Reader reader = null;
Writer writer = null;
try {
// 打開流
reader = new FileReader(path1);
writer = new FileWriter(path2);
// 進(jìn)行拷貝
int ch = -1;
char [] arr=new char[1024];
while ((ch = reader.read(arr)) != -1) {
writer.write(ch);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 關(guān)閉流,注意一定要能執(zhí)行到close()方法匠璧,所以都要放到finally代碼塊中
try {
if (reader != null) {
reader.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
同樣可以用TWR風(fēng)格進(jìn)行簡寫桐款,將大量finally中的資源關(guān)閉操作省略掉,聲明賦值語句放入到try()中即可夷恍。
4魔眨、緩沖流
使用字符流緩沖區(qū)拷貝文本文件可以提高效率,Reader
有一個子類BufferedReader
, 子類繼承父類顯然子類可以重寫父類的方法可以增加自己的新方法遏暴。例如一次讀一行就是常用的操作.那么BufferedReader
類就提供了這個方法,可以查看readLine()
方法具備 一次讀取一個文本行的功能侄刽。很顯然,該子類可以對功能進(jìn)行增強(qiáng)。
private static void copyFile(File srcFile, File destFile)throws IOException {
// 創(chuàng)建字符輸入流
FileReader fr = new FileReader(srcFile);
// 創(chuàng)建字符輸出流
FileWriter fw = new FileWriter(destFile);
// 字符輸入流的緩沖流
BufferedReader br = new BufferedReader(fr);
// 字符輸出流的緩沖流
BufferedWriter bw = new BufferedWriter(fw);
String line = null;
// 一次讀取一行
while ((line = br.readLine()) != null) {
// 一次寫出一行.
bw.write(line);
// 刷新緩沖
bw.flush();
// 進(jìn)行換行,由于readLine方法默認(rèn)沒有換行.需要手動換行
bw.newLine();
}
// 關(guān)閉流
br.close();
bw.close();
}
三朋凉、裝飾器模式:
使用分層對象來動態(tài)透明的向單個對象中添加責(zé)任(功能)州丹。裝飾器指定包裝在最初的對象周圍的所有對象都具有相同的基本接口。某些對象是可裝飾的杂彭,可以通過將其他類包裝在這個可裝飾對象的四周墓毒,來將功能分層。裝飾器必須具有和他所裝飾的對象相同的接口亲怠。Java I/O類庫需要多種不同的功能組合蚁鳖,所以使用了裝飾器模式。Filterxxx
類是JavaIO
提供的裝飾器基類赁炎,即我們要想實(shí)現(xiàn)一個新的裝飾器,就要繼承這些類钾腺。
繼承實(shí)現(xiàn)的增強(qiáng)類:
優(yōu)點(diǎn):代碼結(jié)構(gòu)清晰徙垫,而且實(shí)現(xiàn)簡單
缺點(diǎn):對于每一個的需要增強(qiáng)的類都要創(chuàng)建具體的子類來幫助其增強(qiáng),這樣會導(dǎo)致繼承體系過于龐大放棒。
修飾模式實(shí)現(xiàn)的增強(qiáng)類:
優(yōu)點(diǎn):內(nèi)部可以通過多態(tài)技術(shù)對多個需要增強(qiáng)的類進(jìn)行增強(qiáng)
缺點(diǎn):需要內(nèi)部通過多態(tài)技術(shù)維護(hù)需要增強(qiáng)的類的實(shí)例姻报。進(jìn)而使得代碼稍微復(fù)雜。
四间螟、面試總結(jié):
1吴旋、為了提高讀寫性能,可以采用什么流
針對讀寫對象的不同厢破,字節(jié)流可以采用帶緩沖區(qū)的BufferedInputStream和BufferedOutputStream荣瑟,字符流可以采用帶緩沖區(qū)的BufferedReader和BufferedWriter。
2摩泪、Java中有幾種類型的流
字節(jié)流和字符流笆焰。字節(jié)流繼承于InputStream、OutputStream
见坑,字符流繼承于Reader嚷掠、Writer
。在java.io
包中還有許多其他的流荞驴,主要是為了提高性能和使用方便不皆。關(guān)于Java的I/O需要注意的有兩點(diǎn):一是兩種對稱性(輸入和輸出的對稱性,字節(jié)和字符的對稱性)熊楼;二是兩種設(shè)計模式(適配器模式和裝潢模式)霹娄。另外Java中的流不同于C#的是它只有一個維度一個方向。
3、JDK 為每種類型的流提供了一些抽象類以供繼承项棠,分別是哪些類
Java中的流分為兩種悲雳,一種是字節(jié)流,另一種是字符流香追,分別由四個抽象類來表示(每種流包括輸入和輸出兩種所以一共四個):InputStream合瓢,OutputStream,Reader透典,Writer
晴楔。Java中其他多種多樣變化的流均是由它們派生出來的.
4、對文本文件操作用什么I/O流峭咒?
FileReader/FileWriter
5税弃、對各種基本數(shù)據(jù)類型和String類型的讀寫,采用什么流凑队? DataInputStream则果、DataOutputStream
6、能指定字符編碼的 I/O 流類型是什么漩氨?
BufferedReader/BufferedWriter
BufferedInputStream/BufferedOutputStream
回答以上問題需要分清各個IO流子類的應(yīng)用場景:
FileInputStream/FileOutputStream
需要逐個字節(jié)處理原始二進(jìn)制流的時候使用西壮,效率低下。
FileReader/FileWriter
需要組個字符處理的時候使用叫惊。
StringReader/StringWriter
需要處理字符串的時候款青,可以將字符串保存為字符數(shù)組。
PrintStream/PrintWriter
用來包裝FileOutputStream
對象霍狰,方便直接將String
字符串寫入文件 抡草。
Scanner
用來包裝System.in
流,很方便地將輸入的String
字符串轉(zhuǎn)換成需要的數(shù)據(jù)類型蔗坯。
InputStreamReader/OutputStreamReader
, 字節(jié)和字符的轉(zhuǎn)換橋梁康震,在網(wǎng)絡(luò)通信或者處理鍵盤輸入的時候用。
BufferedReader/BufferedWriter
宾濒,BufferedInputStream/BufferedOutputStream
签杈, 緩沖流用來包裝字節(jié)流后者字符流,提升IO性能鼎兽,BufferedReader
還可以方便地讀取一行答姥,簡化編程。
SequenceInputStream(InputStream s1, InputStream s2)
序列流谚咬,合并流對象時使用.
ObjectInputStream鹦付、ObjectOutputStream
,方法用于序列化對象并將它們寫入一個流择卦,另一個方法用于讀取流并反序列化對象敲长。
ByteArrayInputStream郎嫁、ByteArrayOutputStream
,操作數(shù)組
DataInputStream祈噪、DataOutputStream
操作基本數(shù)據(jù)類型和字符串泽铛。
7、寫一個方法辑鲤,輸入一個文件名和一個字符串盔腔,統(tǒng)計這個字符串在這個文件中出現(xiàn)的次數(shù)。
public final class MyUtil {
// 工具類中的方法都是靜態(tài)方式訪問的因此將構(gòu)造器私有不允許創(chuàng)建對象(絕對好習(xí)慣)
private MyUtil() {
throw new AssertionError();
}
/**
* 統(tǒng)計給定文件中給定字符串的出現(xiàn)次數(shù)
*
* @param filename 文件名
* @param word 字符串
* @return 字符串在文件中出現(xiàn)的次數(shù)
*/
public static int countWordInFile(String filename, String word) {
int counter = 0;
try (FileReader fr = new FileReader(filename)) {
try (BufferedReader br = new BufferedReader(fr)) {
String line = null;
while ((line = br.readLine()) != null) {
int index = -1;
while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
counter++;
line = line.substring(index + word.length());
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return counter;
}
}