摘要:字節(jié)流處理字節(jié)數(shù)據(jù)的流對象。寫入字符流的抽象類。是字符流通向字節(jié)流的橋梁可使用指定的將要寫入流中的字符編碼成字節(jié)。字節(jié)流是表示字節(jié)輸入流的所有類的超類。轉(zhuǎn)換流的最強(qiáng)功能就是基于字節(jié)流編碼表。刪除此抽象路徑名表示的文件或目錄。
IO流(重點(diǎn)理解)
用于處理設(shè)備上數(shù)據(jù)。
流:可以理解數(shù)據(jù)的流動,就是一個(gè)數(shù)據(jù)流。IO流最終要以對象來體現(xiàn),對象都存在IO包中。
流也進(jìn)行分類:
1:輸入流(讀)和輸出流(寫)。
2:因?yàn)樘幚淼臄?shù)據(jù)不同,分為字節(jié)流和字符流。
字節(jié)流:處理字節(jié)數(shù)據(jù)的流對象。設(shè)備上的數(shù)據(jù)無論是圖片或者dvd,文字,它們都以二進(jìn)制存儲的。二進(jìn)制的最終都是以一個(gè)8位為數(shù)據(jù)單元進(jìn)行體現(xiàn),所以計(jì)算機(jī)中的最小數(shù)據(jù)單元就是字節(jié)。意味著,字節(jié)流可以處理設(shè)備上的所有數(shù)據(jù),所以字節(jié)流一樣可以處理字符數(shù)據(jù)。
那么為什么要有字符流呢?因?yàn)樽址總€(gè)國家都不一樣,所以涉及到了字符編碼問題,那么GBK編碼的中文用unicode編碼解析是有問題的,所以需要獲取中文字節(jié)數(shù)據(jù)的同時(shí)+ 指定的編碼表才可以解析正確數(shù)據(jù)。為了方便于文字的解析,所以將字節(jié)流和編碼表封裝成對象,這個(gè)對象就是字符流。只要操作字符數(shù)據(jù),優(yōu)先考慮使用字符流體系。
注意:流的操作只有兩種:讀和寫。
流的體系因?yàn)楣δ懿煌怯泄残詢?nèi)容,不斷抽取,形成繼承體系。該體系一共有四個(gè)基類,而且都是抽象類。
字節(jié)流:InputStream OutputStream
字符流:Reader Writer
public static void main(String[] args) throws IOException { //讀、寫都會發(fā)生IO異常
/*
1:創(chuàng)建一個(gè)字符輸出流對象,用于操作文件。該對象一建立,就必須明確數(shù)據(jù)存儲位置,是一個(gè)文件。
2:對象產(chǎn)生后,會在堆內(nèi)存中有一個(gè)實(shí)體,同時(shí)也調(diào)用了系統(tǒng)底層資源,在指定的位置創(chuàng)建了一個(gè)存儲數(shù)據(jù)的文件。
3:如果指定位置,出現(xiàn)了同名文件,文件會被覆蓋。
*/
FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException
/*
調(diào)用Writer類中的write方法寫入字符串。字符串并未直接寫入到目的地中,而是寫入到了流中,(其實(shí)是寫入到內(nèi)存緩沖區(qū)中)。怎么把數(shù)據(jù)弄到文件中?
*/
fw.write("abcde");
fw.flush(); // 刷新緩沖區(qū),將緩沖區(qū)中的數(shù)據(jù)刷到目的地文件中。
fw.close(); // 關(guān)閉流,其實(shí)關(guān)閉的就是java調(diào)用的系統(tǒng)底層資源。在關(guān)閉前,會先刷新該流。
}
close()和flush()的區(qū)別:
flush():將緩沖區(qū)的數(shù)據(jù)刷到目的地中后,流可以使用。
io異常的處理方式:io一定要寫finally;
FileWriter寫入數(shù)據(jù)的細(xì)節(jié):
1:window中的換行符:rn兩個(gè)符號組成。 linux:n。
2:續(xù)寫數(shù)據(jù),只要在構(gòu)造函數(shù)中傳入新的參數(shù)true。
3:目錄分割符:window /
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("demo.txt",true);
fw.write("abcde");
}
catch (IOException e ){
System.out.println(e.toString()+"....");
}
finally{
if(fw!=null)
try{
fw.close();
}
catch (IOException e){
System.out.println("close:"+e.toString());
}
}
FileReader:使用Reader體系,讀取一個(gè)文本文件中的數(shù)據(jù)。返回 -1 ,標(biāo)志讀到結(jié)尾。
import java.io.*;
class FileReaderDemo {
public static void main(String[] args) throws IOException {
/*
創(chuàng)建可以讀取文本文件的流對象,F(xiàn)ileReader讓創(chuàng)建好的流對象和指定的文件相關(guān)聯(lián)。
*/
FileReader fr = new FileReader("demo.txt");
int ch = 0;
while((ch = fr.read())!= -1) { //條件是沒有讀到結(jié)尾
System.out.println((char)ch); //調(diào)用讀取流的read方法,讀取一個(gè)字符。
}
fr.close();
}
讀取數(shù)據(jù)的第二種方式:第二種方式較為高效,自定義緩沖區(qū)。
import java.io.*;
class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("demo.txt"); //創(chuàng)建讀取流對象和指定文件關(guān)聯(lián)。
//因?yàn)橐褂胷ead(char[])方法,將讀取到字符存入數(shù)組。所以要?jiǎng)?chuàng)建一個(gè)字符數(shù)組,一般數(shù)組的長度都是1024的整數(shù)倍。
char[] buf = new char[1024];
int len = 0;
while(( len=fr.read(buf)) != -1) {
System.out.println(new String(buf,0,len));
}
fr.close();
}
IO中的使用到了一個(gè)設(shè)計(jì)模式:裝飾設(shè)計(jì)模式。
裝飾設(shè)計(jì)模式解決:對一組類進(jìn)行功能的增強(qiáng)。
包裝:寫一個(gè)類(包裝類)對被包裝對象進(jìn)行包裝;
1、包裝類和被包裝對象要實(shí)現(xiàn)同樣的接口;
2、包裝類要持有一個(gè)被包裝對象;
3、包裝類在實(shí)現(xiàn)接口時(shí),大部分方法是靠調(diào)用被包裝對象來實(shí)現(xiàn)的,對于需要修改的方法我們自己實(shí)現(xiàn);
字符流:
Reader:用于讀取字符流的抽象類。子類必須實(shí)現(xiàn)的方法只有 read(char[], int, int) 和 close()。
|---BufferedReader:從字符輸入流中讀取文本,緩沖各個(gè)字符,從而實(shí)現(xiàn)字符、數(shù)組和行的高效讀取。 可以指定緩沖區(qū)的大小,或者可使用默認(rèn)的大小。大多數(shù)情況下,默認(rèn)值就足夠大了。 |---LineNumberReader:跟蹤行號的緩沖字符輸入流。此類定義了方法 setLineNumber(int) 和 getLineNumber(),它們可分別用于設(shè)置和獲取當(dāng)前行號。 |---InputStreamReader:是字節(jié)流通向字符流的橋梁:它使用指定的 charset 讀取字節(jié)并將其解碼為字符。它使用的字符集可以由名稱指定或顯式給定,或者可以接受平臺默認(rèn)的字符集。 |---FileReader:用來讀取字符文件的便捷類。此類的構(gòu)造方法假定默認(rèn)字符編碼和默認(rèn)字節(jié)緩沖區(qū)大小都是適當(dāng)?shù)摹R约褐付ㄟ@些值,可以先在 FileInputStream 上構(gòu)造一個(gè) InputStreamReader。 |---CharArrayReader: |---StringReader:
Writer:寫入字符流的抽象類。子類必須實(shí)現(xiàn)的方法僅有 write(char[], int, int)、flush() 和 close()。
|---BufferedWriter:將文本寫入字符輸出流,緩沖各個(gè)字符,從而提供單個(gè)字符、數(shù)組和字符串的高效寫入。 |---OutputStreamWriter:是字符流通向字節(jié)流的橋梁:可使用指定的 charset 將要寫入流中的字符編碼成字節(jié)。它使用的字符集可以由名稱指定或顯式給定,否則將接受平臺默認(rèn)的字符集。 |---FileWriter:用來寫入字符文件的便捷類。此類的構(gòu)造方法假定默認(rèn)字符編碼和默認(rèn)字節(jié)緩沖區(qū)大小都是可接受的。要自己指定這些值,可以先在 FileOutputStream 上構(gòu)造一個(gè) OutputStreamWriter。 |---PrintWriter: |---CharArrayWriter: |---StringWriter:
字節(jié)流:
InputStream:是表示字節(jié)輸入流的所有類的超類。
|--- FileInputStream:從文件系統(tǒng)中的某個(gè)文件中獲得輸入字節(jié)。哪些文件可用取決于主機(jī)環(huán)境。FileInputStream 用于讀取諸如圖像數(shù)據(jù)之類的原始字節(jié)流。要讀取字符流,請考慮使用 FileReader。 |--- FilterInputStream:包含其他一些輸入流,它將這些流用作其基本數(shù)據(jù)源,它可以直接傳輸數(shù)據(jù)或提供一些額外的功能。 |--- BufferedInputStream:該類實(shí)現(xiàn)緩沖的輸入流。 |--- Stream: |--- ObjectInputStream: |--- PipedInputStream:
OutputStream:此抽象類是表示輸出字節(jié)流的所有類的超類。
|--- FileOutputStream:文件輸出流是用于將數(shù)據(jù)寫入 File 或 FileDescriptor 的輸出流。 |--- FilterOutputStream:此類是過濾輸出流的所有類的超類。 |--- BufferedOutputStream:該類實(shí)現(xiàn)緩沖的輸出流。 |--- PrintStream: |--- DataOutputStream: |--- ObjectOutputStream: |--- PipedOutputStream:
緩沖區(qū)是提高效率用的,給誰提高呢?
BufferedWriter:是給字符輸出流提高效率用的,那就意味著,緩沖區(qū)對象建立時(shí),必須要先有流對象。明確要提高具體的流對象的效率。
FileWriter fw = new FileWriter("bufdemo.txt");
BufferedWriter bufw = new BufferedWriter(fw);//讓緩沖區(qū)和指定流相關(guān)聯(lián)。
for(int x=0; x<4; x++){
bufw.write(x+"abc");
bufw.newLine(); //寫入一個(gè)換行符,這個(gè)換行符可以依據(jù)平臺的不同寫入不同的換行符。
bufw.flush();//對緩沖區(qū)進(jìn)行刷新,可以讓數(shù)據(jù)到目的地中。
}
BufferedReader:
FileReader fr = new FileReader("bufdemo.txt");
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null){ //readLine方法返回的時(shí)候是不帶換行符的。
System.out.println(line);
}
//記住,只要一讀取鍵盤錄入,就用這句話。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//輸出到控制臺
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
bufw.write(line.toUpperCase());//將輸入的字符轉(zhuǎn)成大寫字符輸出
bufw.newLine();
bufw.flush();
}
bufw.close();
流對象:其實(shí)很簡單,就是讀取和寫入。但是因?yàn)楣δ艿牟煌鞯捏w系中提供N多的對象。那么開始時(shí),到底該用哪個(gè)對象更為合適呢?這就需要明確流的操作規(guī)律。
流的操作規(guī)律:
1,明確源和目的。
數(shù)據(jù)源:就是需要讀取,可以使用兩個(gè)體系:InputStream、Reader;
數(shù)據(jù)匯:就是需要寫入,可以使用兩個(gè)體系:OutputStream、Writer;
2,操作的數(shù)據(jù)是否是純文本數(shù)據(jù)?
如果是:數(shù)據(jù)源:Reader
數(shù)據(jù)匯:Writer
如果不是:數(shù)據(jù)源:InputStream
數(shù)據(jù)匯:OutputStream
3,雖然確定了一個(gè)體系,但是該體系中有太多的對象,到底用哪個(gè)呢?
明確操作的數(shù)據(jù)設(shè)備。
數(shù)據(jù)源對應(yīng)的設(shè)備:硬盤(File),內(nèi)存(數(shù)組),鍵盤(System.in)
數(shù)據(jù)匯對應(yīng)的設(shè)備:硬盤(File),內(nèi)存(數(shù)組),控制臺(System.out)。
4,需要在基本操作上附加其他功能嗎?比如緩沖。
如果需要就進(jìn)行裝飾。
轉(zhuǎn)換流特有功能:轉(zhuǎn)換流可以將字節(jié)轉(zhuǎn)成字符,原因在于,將獲取到的字節(jié)通過查編碼表獲取到指定對應(yīng)字符。
轉(zhuǎn)換流的最強(qiáng)功能就是基于 字節(jié)流 + 編碼表 。沒有轉(zhuǎn)換,沒有字符流。
發(fā)現(xiàn)轉(zhuǎn)換流有一個(gè)子類就是操作文件的字符流對象:
InputStreamReader
|--FileReader
OutputStreamWriter
|--FileWrier
想要操作文本文件,必須要進(jìn)行編碼轉(zhuǎn)換,而編碼轉(zhuǎn)換動作轉(zhuǎn)換流都完成了。所以操作文件的流對象只要繼承自轉(zhuǎn)換流就可以讀取一個(gè)字符了。
但是子類有一個(gè)局限性,就是子類中使用的編碼是固定的,是本機(jī)默認(rèn)的編碼表,對于簡體中文版的系統(tǒng)默認(rèn)碼表是GBK。
FileReader fr = new FileReader("a.txt");
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
以上兩句代碼功能一致,
如果僅僅使用平臺默認(rèn)碼表,就使用FileReader fr = new FileReader("a.txt"); //因?yàn)楹喕?/p>
如果需要制定碼表,必須用轉(zhuǎn)換流。
轉(zhuǎn)換流 = 字節(jié)流+編碼表。
轉(zhuǎn)換流的子類File = 字節(jié)流 + 默認(rèn)編碼表。
File類:將文件系統(tǒng)中的文件和文件夾封裝成了對象。提供了更多的屬性和行為可以對這些文件和文件夾進(jìn)行操作。這些是流對象辦不到的,因?yàn)榱髦徊僮鲾?shù)據(jù)。
File類常見方法:
1:創(chuàng)建。
boolean createNewFile():在指定目錄下創(chuàng)建文件,如果該文件已存在,則不創(chuàng)建。而對操作文件的輸出流而言,輸出流對象已建立,就會創(chuàng)建文件,如果文件已存在,會覆蓋。除非續(xù)寫。
boolean mkdir():創(chuàng)建此抽象路徑名指定的目錄。
boolean mkdirs():創(chuàng)建多級目錄。
2:刪除。
boolean delete():刪除此抽象路徑名表示的文件或目錄。
void deleteOnExit():在虛擬機(jī)退出時(shí)刪除。
注意:在刪除文件夾時(shí),必須保證這個(gè)文件夾中沒有任何內(nèi)容,才可以將該文件夾用delete刪除。
window的刪除動作,是從里往外刪。注意:java刪除文件不走回收站。要慎用。
3:獲取.
long length():獲取文件大小。
String getName():返回由此抽象路徑名表示的文件或目錄的名稱。
String getPath():將此抽象路徑名轉(zhuǎn)換為一個(gè)路徑名字符串。
String getAbsolutePath():返回此抽象路徑名的絕對路徑名字符串。
String getParent():返回此抽象路徑名父目錄的抽象路徑名,如果此路徑名沒有指定父目錄,則返回 null。
long lastModified():返回此抽象路徑名表示的文件最后一次被修改的時(shí)間。
File.pathSeparator:返回當(dāng)前系統(tǒng)默認(rèn)的路徑分隔符,windows默認(rèn)為 “;”。
File.Separator:返回當(dāng)前系統(tǒng)默認(rèn)的目錄分隔符,windows默認(rèn)為 “”。
4:判斷:
boolean exists():判斷文件或者文件夾是否存在。
boolean isDirectory():測試此抽象路徑名表示的文件是否是一個(gè)目錄。
boolean isFile():測試此抽象路徑名表示的文件是否是一個(gè)標(biāo)準(zhǔn)文件。
boolean isHidden():測試此抽象路徑名指定的文件是否是一個(gè)隱藏文件。
boolean isAbsolute():測試此抽象路徑名是否為絕對路徑名。
5:重命名。
boolean renameTo(File dest):可以實(shí)現(xiàn)移動的效果。剪切+重命名。
String[] list():列出指定目錄下的當(dāng)前的文件和文件夾的名稱。包含隱藏文件。
如果調(diào)用list方法的File 對象中封裝的是一個(gè)文件,那么list方法返回?cái)?shù)組為null。如果封裝的對象不存在也會返回null。只有封裝的對象存在并且是文件夾時(shí),這個(gè)方法才有效。遞歸:就是函數(shù)自身調(diào)用自身。
什么時(shí)候用遞歸呢?
當(dāng)一個(gè)功能被重復(fù)使用,而每一次使用該功能時(shí)的參數(shù)不確定,都由上次的功能元素結(jié)果來確定。
簡單說:功能內(nèi)部又用到該功能,但是傳遞的參數(shù)值不確定。(每次功能參與運(yùn)算的未知內(nèi)容不確定)。
遞歸的注意事項(xiàng):
1:一定要定義遞歸的條件。
2:遞歸的次數(shù)不要過多。容易出現(xiàn) StackOverflowError 棧內(nèi)存溢出錯(cuò)誤。
Java.util.Properties:一個(gè)可以將鍵值進(jìn)行持久化存儲的對象。Map--Hashtable的子類。
Map
|--Hashtable
|--Properties:用于屬性配置文件,鍵和值都是字符串類型。
特點(diǎn):1:可以持久化存儲數(shù)據(jù)。2:鍵值都是字符串。3:一般用于配置文件。
|-- load():將流中的數(shù)據(jù)加載進(jìn)集合。
原理:其實(shí)就是將讀取流和指定文件相關(guān)聯(lián),并讀取一行數(shù)據(jù),因?yàn)閿?shù)據(jù)是規(guī)則的key=value,所以獲取一行后,通過 = 對該行數(shù)據(jù)進(jìn)行切割,左邊就是鍵,右邊就是值,將鍵、值存儲到properties集合中。
|-- store():寫入各個(gè)項(xiàng)后,刷新輸出流。
以下介紹IO包中擴(kuò)展功能的流對象:基本都是裝飾設(shè)計(jì)模式。
Java.io.outputstream.PrintStream:打印流
1:提供了更多的功能,比如打印方法。可以直接打印任意類型的數(shù)據(jù)。
2:它有一個(gè)自動刷新機(jī)制,創(chuàng)建該對象,指定參數(shù),對于指定方法可以自動刷新。
3:它使用的本機(jī)默認(rèn)的字符編碼.
4:該流的print方法不拋出IOException。
該對象的構(gòu)造函數(shù)。
PrintStream(File file) :創(chuàng)建具有指定文件且不帶自動行刷新的新打印流。
PrintStream(File file, String csn) :創(chuàng)建具有指定文件名稱和字符集且不帶自動行刷新的新打印流。
PrintStream(OutputStream out) :創(chuàng)建新的打印流。
PrintStream(OutputStream out, boolean autoFlush) :創(chuàng)建新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding) :創(chuàng)建新的打印流。
PrintStream(String fileName) :創(chuàng)建具有指定文件名稱且不帶自動行刷新的新打印流。
PrintStream(String fileName, String csn)
PrintStream可以操作目的:1:File對象。2:字符串路徑。3:字節(jié)輸出流。
前兩個(gè)都JDK1.5版本才出現(xiàn)。而且在操作文本文件時(shí),可指定字符編碼了。
當(dāng)目的是一個(gè)字節(jié)輸出流時(shí),如果使用的println方法,可以在printStream對象上加入一個(gè)true參數(shù)。這樣對于println方法可以進(jìn)行自動的刷新,而不是等待緩沖區(qū)滿了再刷新。最終print方法都將具體的數(shù)據(jù)轉(zhuǎn)成字符串,而且都對IO異常進(jìn)行了內(nèi)部處理。
既然操作的數(shù)據(jù)都轉(zhuǎn)成了字符串,那么使用PrintWriter更好一些。因?yàn)镻rintWrite是字符流的子類,可以直接操作字符數(shù)據(jù),同時(shí)也可以指定具體的編碼。PrintWriter:具備了PrintStream的特點(diǎn)同時(shí),還有自身特點(diǎn):
該對象的目的地有四個(gè):1:File對象。2:字符串路徑。3:字節(jié)輸出流。4:字符輸出流。
開發(fā)時(shí)盡量使用PrintWriter。
方法中直接操作文件的第二參數(shù)是編碼表。
直接操作輸出流的,第二參數(shù)是自動刷新。
//讀取鍵盤錄入將數(shù)據(jù)轉(zhuǎn)成大寫顯示在控制臺.
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//源:鍵盤輸入
//目的:把數(shù)據(jù)寫到文件中,還想自動刷新。
PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//設(shè)置true后自動刷新
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
out.println(line.toUpperCase());//轉(zhuǎn)大寫輸出
}
//注意:System.in,System.out這兩個(gè)標(biāo)準(zhǔn)的輸入輸出流,在jvm啟動時(shí)已經(jīng)存在了。隨時(shí)可以使用。當(dāng)jvm結(jié)束了,這兩個(gè)流就結(jié)束了。但是,當(dāng)使用了顯示的close方法關(guān)閉時(shí),這兩個(gè)流在提前結(jié)束了。
out.close();
SequenceInputStream:序列流,作用就是將多個(gè)讀取流合并成一個(gè)讀取流。實(shí)現(xiàn)數(shù)據(jù)合并。
表示其他輸入流的邏輯串聯(lián)。它從輸入流的有序集合開始,并從第一個(gè)輸入流開始讀取,直到到達(dá)文件末尾,接著從第二個(gè)輸入流讀取,依次類推,直到到達(dá)包含的最后一個(gè)輸入流的文件末尾為止。
這樣做,可以更方便的操作多個(gè)讀取流,其實(shí)這個(gè)序列流內(nèi)部會有一個(gè)有序的集合容器,用于存儲多個(gè)讀取流對象。
該對象的構(gòu)造函數(shù)參數(shù)是枚舉,想要獲取枚舉,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中沒有枚舉,只有自己去創(chuàng)建枚舉對象。
但是方法怎么實(shí)現(xiàn)呢?因?yàn)槊杜e操作的是具體集合中的元素,所以無法具體實(shí)現(xiàn),但是枚舉和迭代器是功能一樣的,所以,可以用迭代替代枚舉。
合并原理:多個(gè)讀取流對應(yīng)一個(gè)輸出流。
切割原理:一個(gè)讀取流對應(yīng)多個(gè)輸出流。
import java.io.*;
import java.util.*;
class SplitFileDemo{
private static final String CFG = ".properties";
private static final String SP = ".part";
public static void main(String[] args) throws IOException{
File file = new File("c: