Java---IO流知識總結
一、結構圖
二、分類
IO 流:用於處理設備上的數據。設備:硬盤,內存,鍵盤錄入。
IO流分類:
1,根據處理的數據類型不同:字節流和字符流。
2,根據流向不同:輸入流和輸出流。
字符流的由來:
因爲文件編碼的不同,而有了對字符進行高效操作的字符流對象。
原理:其實就是基於字節流讀取字節時,去查了指定的碼錶。
字節流和字符流的區別:
1,字節流讀取的時候,讀到一個字節就返回一個字節。
字符流使用了字節流讀到一個或多個字節(中文對應的字節數是兩個,在 UTF-8 碼錶中是 3 個字節)時。先去查指定的編碼表,將查到的字符返回。
2,字節流可以處理所有類型數據,如圖片,mp3,avi。 而字符流只能處理字符數據。
結論:只要是處理純文本數據,就要優先考慮使用字符流。除此之外都用字節流。
IO 體系所具備的基本功能就有兩個:讀 和 寫。
1,字節流:InputStream(讀),OutputStream(寫)。
2,字符流:Reader(讀),Writer(寫)。
基本的讀寫操作方式:
因爲數據通常都以文件形式存在,所以就要找到 IO 體系中可以用於操作文件的流對象。通過名稱可以更容易獲取該對象。
因爲 IO 體系中的子類名後綴,絕大部分是父類名稱。而前綴,都是體現子類功能的名字。
字符流:
Reader
|--InputStreamReader
|--FileReader:專門用於處理文件的字符讀取流對象。
Writer
|--OutputStreamWriter
|--FileWriter:專門用於處理文件的字符寫入流對象。
Reader 中的常見的方法:
1,int read():讀取一個字符。返回的是讀到的那個字符。如果讀到流的末尾,返回-1.
2,int read(char[]):將讀到的字符存入指定的數組中,返回的是讀到的字符個數,也就是往數組裏 裝的元素的個數。如果讀到流的末尾,返回-1.
3,close(): 讀取字符其實用的是 window 系統的功能,就希望使用完畢後,進行資源的釋 放。
Writer 中的常見的方法:
1,write(ch): 將一個字符寫入到流中。
2,write(char[]): 將一個字符數組寫入到流中。
3,write(String): 將一個字符串寫入到流中。
4,flush():刷新流,將流中的數據刷新到目的地中,流還存在。
5,close():關閉資源:在關閉前會先調用 flush(),刷新流中的數據去目的地。然流 關閉。
FileWriter:
該類沒有特有的方法。只有自己的構造函數。
特點:
1,用於處理文本文件。
2,該類中有默認的編碼表,
3,該類中有臨時緩衝。
構造函數:在寫入流對象初始化時,必須要有一個存儲數據的目的地。
FileWriter(String filename):
該構造函數做了什麼事情呢?
1,調用系統資源。
2,在指定位置,創建一個文件。
注意:如果該文件已存在,將會被覆蓋。
FileWriter(String filename,boolean append):該構造函數:
當傳入的 boolean 類型值爲 true 時,會在指定文件末尾處進行數 據的續寫。
FileReader:
1,用於讀取文本文件的流對象。
2,用於關聯文本文件。
構造函數:在讀取流對象初始化的時候,必須要指定一個被讀取的文件。
如果該文件不存在會發生 FileNotFoundException.
FileReader(String filename);
對於讀取或者寫入流對象的構造函數,以及讀寫方法, 還有刷新關閉功能都會拋出 IOException 或其子類。所以都要進行處理。或者 throws 拋出,或者 try catch處理;
另一個小細節:
當指定絕對路徑時,定義目錄分隔符有兩種方式:
1,反斜線 但是一定要寫兩個。\\ new FileWriter("c:\\demo.txt");
2,斜線 /
寫一個即可。 new FileWriter("c:/demo.txt”);
字符流的緩衝區:
緩衝區的出現提高了對流的操作效率。
原理:其實就是將數組進行封裝。
對應的對象:
BufferedWriter:
特有方法:newLine():寫一個換行符,跨平臺的換行符。
BufferedReader:
特有方法:readLine():一次讀一行,到行標記時,將行標記之前的字符數據作爲字符串返 回。當讀到末尾時,返回 null。不包含回車符。
在使用緩衝區對象時,要明確,緩衝的存在是爲了增強流的功能而存在, 所以在建立緩衝區對象時,要先有流對象存在。
其實緩衝內部就是在使用流對象的方法,只不過加入了數組對數據進行了臨時存儲。爲 了提高操作數據的效率。
代碼上的體現:
寫入緩衝區對象。
//建立緩衝區對象必須把流對象作爲參數傳遞給緩衝區的構造函數。
BufferedWriter bufw = new BufferedWriter(new FileWriter("buf.txt"));
bufw.write("abce");//將數據寫入到了緩衝區。
bufw.flush();//對緩衝區的數據進行刷新。將數據刷到目的地中。
bufw.close();//關閉緩衝區,其實關閉的是被包裝在內部的流對象。
讀取緩衝區對象。
BufferedReader bufr = new BufferedReader(new FileReader("buf.txt"));
String line = null;
//按照行的形式取出數據。取出的每一個行數據不包含回車符。
while((line=bufr.readLine())!=null){
System.out.println(line);
}
bufr.close();
練習:通過緩衝區的形式,對文本文件進行拷貝。
public static void main(String[] args){
BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
String line = null;
while((line=bufr.readLine())!=null){
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufw.close();
bufr.close();
}
readLine():方法的原理:
其實緩衝區中的該方法,用的還是與緩衝區關聯的流對象的 read 方法。只不過,每一次讀到一個字符,先不進行具體操作,先進行臨時存儲。
當讀取到回車標記時,將臨時容器中存儲的數據一次性返回。
它的出現基於流並增強了流的功能。這也是一種設計模式的體現:裝飾設計模式。 對一組對象進行功能的增強。
該模式和繼承有什麼區別呢?它比繼承有更好的靈活性。通常裝飾類和被裝飾類都同屬與一個父類或者接口。
字節流:
字節流可以操作任何數據。
抽象基類:InputStream,OutputStream。
注意:字符流使用的數組是字符數組。char [] chs
字節流使用的數組是字節數組。byte
[] bt
FileOutputStream fos = new FileOutputStream("a.txt");
fos.write("abcde");//直接將數據寫入到了目的地。
fos.close();//只關閉資源。
FileInputStream fis = new FileInputStream("a.txt");
//fis.available();//獲取關聯的文件的字節數。
//如果文件體積不是很大。可以這樣操作。但是這有一個弊端,就是文件過大,大小超出 jvm 的內容空間時,會內存溢出。
byte[] buf = new byte[fis.available()];//創建一個剛剛好的緩衝區。
fis.read(buf);
System.out.println(new String(buf));
轉換流:
特點:
1,是字節流和字符流之間的橋樑。
2,該流對象中可以對讀取到的字節數據進行指定編碼表的編碼轉換。
什麼時候使用呢?
1,當字節和字符之間有轉換動作時。
2,流操作的數據需要進行編碼表的指定時。
具體的對象體現:
1,InputStreamReader:字節到字符的橋樑。
2,OutputStreamWriter:字符到字節的橋樑。
這兩個流對象是字符流體系中的成員。那麼它們有轉換作用,而本身又是字符流。所以在構造的時候,需要傳入字節流對象進 來。
構造函數:
InputStreamReader(InputStream):通過該構造函數初始化,使用的是本系統默認的編碼表 GBK。
InputStreamReader(InputStream,String charSet):通過該構造函數初始化,可以指定編碼表。
OutputStreamWriter(OutputStream):通過該構造函數初始化,使用的是本系統默認的編 碼錶 GBK。
OutputStreamWriter(OutputStream,String charSet):通過該構造函數初始化,可以指定編碼 表。
操作文件的字符流對象是轉換流的子類。
Reader
|--InputStreamReader
|--FileReader
Writer
|--OutputStreamWriter
|--FileWriter
轉換流中的 read 方法。已經融入了編碼表,在底層調用字節流的 read 方法時將獲取的一個或者多個字節數據進行臨時存儲,並去查指定的編碼表,如果編碼
表沒有指定,查的是默認碼錶。那麼轉流的 read 方法就可以返回一個字符比如中文。
轉換流已經完成了編碼轉換的動作,對於直接操作的文本文件的 FileReaer 而言,就不 用在重新定義了,只要繼承該轉換流,獲取其方法,就可以直接操作文本文件中的字符數據了。
注意:
在使用 FileReader 操作文本數據時,該對象使用的是默認的編碼表。如果要使用指定編碼表時,必須使用轉換流。
FileReader fr = new FileReader("a.txt");//操作 a.txt 的中的數據使用的本系統默認的 GBK 。操作 a.txt 中的數據使用的也是本系統默認的 GBK。
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
這兩句的代碼的意義相同。
如果 a.txt 中的文件中的字符數據是通過 utf-8 的形式編碼。那麼在讀取時,就必須指定編碼表。
那麼轉換流必須使用:InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"utf-8”);
IO 其他對象:
1,打印流,
PrintStream:
是一個字節打印流,System.out
對應的類型就是 PrintStream。它的構造函數可以接收三種數據類型的值。
1,字符串路徑。
2,File 對象。
3,OutputStream。
PrintWriter:
是一個字符打印流。構造函數可以接收四種類型的值。
1,字符串路徑。
2,File 對象。
對於 1,2 類型的數據,還可以指定編碼表。也就是字符集。
3,OutputStream
4,Writer
對於 3,4 類型的數據,可以指定自動刷新。
注意:該自動刷新值爲 true 時,只有三個方法可以用:println,printf,format.
如果想要既有自動刷新,又可執行編碼。如何完成流對象的包裝?
PrintWrter pw = new PrintWriter(new
OutputSteamWriter(new FileOutputStream("a.txt"),"utf-8"),true);
如果想要提高效率。還要使用打印方法。
PrintWrter pw = new PrintWriter(new BufferdWriter(new OutputSteamWriter(new
FileOutputStream("a.txt"),"utf-8")),true);
2,管道流。
PipedInputStream 和 PipedOutputStream
特點:
讀取管道流和寫入管道流可以進行連接。
連接方式:
1,通過兩個流對象的構造函數。
2,通過兩個對象的 connect 方法。
通常兩個流在使用時,需要加入多線程技術,也就是讓讀寫同時運行。注意;對於 read 方法。該方法是阻塞式的,也就是沒有數據的情況,該方法會等待。
3,RandomAccessFile:
該對象並不是流體系中的一員。該對象中封裝了字節流,同時還封裝了一個緩衝區(字節數組),通過內部的指針來操作 數組中的數據。
特點:
1,該對象只能操作文件,所以構造函數接收兩種類型的參數。
a,字符串路徑。
b,File 對象。
2,該對象既可以對文件進行讀取,也可以寫入。
在進行對象實例化時,必須要指定的該對象的操作模式,r rw 等。
該對象中有可以直接操作基本數據類型的方法。
該對象最有特點的方法:
skipBytes():跳過指定的字節數。
seek():指定指針的位置。
getFilePointer():獲取指針的位置。
通過這些方法,就可以完成對一個文件數據的隨機的訪問。想讀哪裏就讀哪裏,想往哪裏寫就往哪裏寫。
該對象功能,可以讀數據,可以寫入數據,如果寫入位置已有數據,會發生數據覆蓋。 也就是可以對數據進行修改。
在使用該對象時,建議數據都是有規則的。或者是分段的。
注意;該對象在實例化時,如果要操作的文件不存在,會自動建立。
如果要操作的文件存在,則不會建立,如果存在的文件有數據,那麼在沒有指定指針位置的情況下,寫入數據,會將文件開頭的數據覆蓋。
可以用於多線程的下載,也就是通過多線程往一個文件中同時存儲數據。
序列流。也稱爲合併流。
SequenceInputStream:
特點:可以將多個讀取流合併成一個流。這樣操作起來很方便。
原理:其實就是將每一個讀取流對象存儲到一個集合中。最後一個流對象結尾作爲這個 流的結尾。
兩個構造函數:
1,SequenceInputStream(InputStream in1,InputStream in2)
可以將兩個讀取流合併成一個流。
2,SequenceInputStream(Enumeration<? extends InputStream> en)
可以將枚舉中的多個流合併成一個流。
作用:可以用於多個數據的合併。
如果要一個對文件數據切割。 一個讀取對應多了輸出。
FileInputStream fis = new FileInputStream("1.mp3");
FileOutputStream fos = null;
byte[] buf = new byte[1024*1024]; //是一個 1MB 的緩衝區。
int len = 0;
int count = 1;
while((len=fis.read(buf))!=-1) {
fos = new FileOutputStream((count++)+".part);
fos.write(buf,0,len);
fos.close();
}
fis.close();
這樣就是將 1.mp3 文件切割成多個碎片文件。想要合併使用 SequenceInputStream 即可。
對於切割後,合併是需要的一些源文件的信息。可以通過配置文件進行存儲。該配置可以通過鍵=值的形式存在。然後通過 Properties 對象進行數據的加載和獲取。
File 類:
該類的出現是對文件系統的中的文件以及文件夾進行對象的封裝。
可以通過對象的思想來操作文件以及文件夾。
1,構造函數:
File(String filename):將一個字符串路徑(相對或者絕對)封裝成 File 對象,該路徑是可存在的,也可以是不存在(需要判斷)。
File(String parent,String child);
File(File parent,String child);
2,特別的字段:separator:跨平臺的目錄分隔符。
例子:File file = new File("c:"+File.separator+"abc"+File.separator+"a.txt");
3,常見方法:
1,創建:
boolean createNewFile () throws IOException: 創建文件,如果被創建的文件已經存在,則不創建。
boolean mkdir( ): 創建文件夾。
boolean mkdirs( ): 創建多級文件夾。
2,刪除:
boolean delete( ):可用於刪除文件或者文件夾。
注意:對於文件夾只能刪除不帶內容的空文件夾,對於帶有內容的文件夾,不可以直接刪除,必須要從裏往外刪除。
void deleteOnExit( ): 刪除動作交給系統完成。無論是否反生異常,系統在退出時執行刪除動作。
3,判斷:
boolean canExecute():
boolean canWrite( ):
boolean canRead( );
boolean exists( ):判斷文件或者文件夾是否存在。
boolean isFile( ): 判斷 File 對象中封裝的是否是文件。
boolean isDirectory( ):判斷 File 對象中封裝的是否是文件夾。
boolean isHidden( ):判斷文件或者文件夾是否隱藏。在獲取硬盤文件或者文件夾 時,
對於系統目錄中的文件,java 是無法訪問的,所以在遍歷,可以避免遍歷 隱藏文件。
4,獲取:
getName( ):獲取文件或者文件夾的名稱。
getPath( ):File 對象中封裝的路徑是什麼,獲取的就是什麼。
getAbsolutePath( ):無論 File 對象中封裝的路徑是什麼,獲取的都是絕對路徑。
getParent( ): 獲取 File 對象封裝文件或者文件夾的父目錄。
注意:如果封裝的是相對路徑,那麼返回的是 null.
long length( ):獲取文件大小。
long lastModified( ):獲取文件或者文件最後一次修改的時間。
static File [] listRoots():獲取的是被系統中有效的盤符。
String[] list( ):獲取指定目錄下當前的文件以及文件夾名稱。
String[] list(Filenamefilter): 可以根據指定的過濾器,過濾後的文件及文件夾名 稱。
File[] listFiles( ):獲取指定目錄下的文件以及文件夾對象。
5,重命名:
renameTo(File):
File f1 = new File("c:\\a.txt");
File f2 = new File("c:\\b.txt");
f1.renameTo(f2);//將 c 盤下的 a.txt 文件改名爲 b.txt 文件。
操作基本數據類型的流對象。
DataInputStream
DataInputStream(InputStream);
操作基本數據類型的方法:
int readInt():一次讀取四個字節,並將其轉成 int 值。
boolean readBoolean():一次讀取一個字節。
short readShort();
long readLong();
剩下的數據類型一樣。
String readUTF():按照 utf-8 修改版讀取字符。注意,它只能讀 writeUTF()寫入的字符 數據。
DataOutputStream
DataOutputStream(OutputStream):
操作基本數據類型的方法:
writeInt(int):一次寫入四個字節。
注意和 write(int)不同。write(int)只將該整數的最低一個 8 位寫入。剩餘三個 8 位丟棄。
writeBoolean(boolean);
writeShort(short);
writeLong(long);
剩下是數據類型也也一樣。
writeUTF(String):按照 utf-8 修改版將字符數據進行存儲。只能通過 readUTF 讀取。
通常只要操作基本數據類型的數據。就需要通過 DataStram 進行包裝。
通常成對使用。
操作數組的流對象
1,操作字節數組
ByteArrayInputStream
ByteArrayOutputStream
toByteArray();
toString();
writeTo(OutputStream);
2,操作字符數組。
CharArrayReader
CharArrayWriter
對於這些流,源是內存。目的也是內存。而且這些流並未調用系統資源。使用的就是內存中的數組。所以這些在使用的時候不需要
close。
操作數組的讀取流在構造時,必須要明確一個數據源。所以要傳入相對應的數組。對於操作數組的寫入流,在構造函數可以使用空參數。因爲它內置了一個可變長度數組 作爲緩衝區。
這幾個流的出現其實就是通過流的讀寫思想在操作數組。類似的對象同理:StringReader,StringWriter
編碼轉換
在 io 中涉及到編碼轉換的流是轉換流和打印流。但是打印流只有輸出。在轉換流中是可以指定編碼表的。默認情況下,都是本機默認的碼錶。GBK. 這個編碼表怎麼來的? System.getProperty("file.encoding");
常見碼錶:
ASCII:美國標準信息交換碼。使用的是 1 個字節的 7 位來表示該表中的字符。
ISO8859-1:拉丁碼錶。使用 1 個字節來表示。
GB2312:簡體中文碼錶。
GBK:簡體中文碼錶,比 GB2312 融入更多的中文文件和符號。
unicode:國際標準碼錶。都用兩個字節表示一個字符。
UTF-8:對 unicode 進行優化,每一個字節都加入了標識頭。
編碼轉換:
字符串 —> 字節數組 :編碼。通過 getBytes(charset);
字節數組-->字符串 : 解碼。通過 String 類的構造函數完成。String(byte[],charset);
如果編錯了,沒救!如果編對了,解錯了,有可能還有救!
String s = "你好";
//編碼。
byte[] b = s.getBytes("GBK");
//解碼。
String s1 = new String(b,"iso8859-1");
System.out.println(s1);
//想要還原。
/* 對 s1 先進行一次解碼碼錶的編碼。獲取原字節數據。
然後在對原字節數據進行指定編碼表的解碼。
*/
byte[] b1 = s1.getBytes("iso8859-1");
String s2 = new String(b1,"gbk");
System.out.println(s2);//你好。
這種情況在 tomcat 服務器會出現。因爲 tomcat 服務器默認是 iso8859-1 的編碼表。所以客戶端通過瀏覽器向服務端通過 get 提交方式提交中文數據時,服務端獲取到會使用 ISO8859-1 進行中文數據解碼。會出現亂碼。這時就必須要對獲取的數據進行
iso8859-1 編碼。然後在按照頁面指定的編碼表進行解碼即可。
而對於 post 提交,這種方法也通用。但是 post 有更好的解決方式。request.setCharacterEncoding("utf-8");即可。所以建立客戶端提交使用 post 提交方式。
相關知識:
1.如果啓用了自動刷新,則只有在調用 println、printf 或 format 的能夠實現數據寫入到流所對應的文件
2.序列化流: 把對象寫入到流中
ObjectOutputStream
構造方法:
ObjectOutputStream()
ObjectOutputStream(OutputStream out)
方法:
public final void writeObject(Object obj) throws IOException
將指定的對象寫入 ObjectOutputStream
反序列化流:從流中讀取對象
ObjectInputStream
構造方法:
ObjectInputStream()
ObjectInputStream(InputStream in)
方法:
public final Object readObject() throws IOException, ClassNotFoundException
從 ObjectInputStream 讀取對象
注意: 使用序列化與反序列化的類通過實現 java.io.Serializable 接口以啓用其序列化功能。
未實現此接口的類將無法使其任何狀態序列化或反序列化,序列化接口沒有方法或字段,僅用於標識可序列化的語義。
使用transient關鍵字聲明不需要序列化的成員變量。
Properties和IO流的結合使用
public void load(InputStream stream)
public void load(Reader reader) 把流所對應的文件中的數據,讀取到集合中
public void store(OutputStream stream,String comments)
public void store(Writer writer,String comments) 把集合中的數據,存儲到流所對應的文件中
close()和flush()的區別:
flush():將緩衝區的數據刷到目的地中後,流可以使用。
close():將緩衝區的數據刷到目的地中後,流就關閉了,該方法主要用於結束調用的底層資源。這個動作一定做。
//記住,只要一讀取鍵盤錄入,就用這句話。
BufferedReader bufr = new BufferedReader(new
InputStreamReader(System.in));
//輸出到控制檯
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
流的操作規律:
1,明確數據源和目的地。(明確輸入流還是輸出流。)
數據源:就是需要讀取,可以使用兩個體系:InputStream、Reader;
目的地:就是需要寫入,可以使用兩個體系:OutputStream、Writer;
2,操作的數據是否是純文本數據?(明確字符流還是字節流)
如果是:數據源:Reader
目的地:Writer
如果不是:數據源:InputStream
目的地:OutputStream
3,雖然確定了一個體系,但是該體系中有太多的對象,到底用哪個呢?
明確操作的數據設備。
數據源對應的設備:硬盤(File),內存(數組),鍵盤(System.in)
數據匯對應的設備:硬盤(File),內存(數組),控制檯(System.out)。
4,需要在基本操作上附加其他功能嗎?比如緩衝。
如果需要就進行裝飾。
數據匯:一個文件,硬盤。
既然是數據匯,那麼一定是輸出流,可以用的 OutputStream,Writer。往文件中存儲的都是文本數據,那麼可以使用字符流較爲方便:Writer.
因爲操作的是一個文件。所以使用 Writer 中的 FileWriter。
是否要提高效率呢?是,那就使用 BufferedWriter.
BufferedWriter bufr = new BufferedWriter(new FileWriter("a.txt"));
需求:
1,將鍵盤錄入的數據存儲到一個文件中。
數據源:System.in。
既然是源,使用的就是輸入流,可用的體系有 InputStream,Reader。
因爲鍵盤錄入進來的一定是純文本數據,所以可以使用專門操作字符數據的 Reader。發現System.in
對應的流是字節讀取流。所以要將其進行轉換,將字節轉成 字符即可。所以要使用 Reader 體系中:InputStreamReader。
接下來,是否需要提高效率呢?如果需要,那麼就加入字符流的緩衝區: BufferedReader
BufferedReader bur = new BufferedReader(new InputStreamReader(System.in));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.