一、什麼是流?
流就是位元組序列的抽象概念,能被連續讀取數據的數據源和能被連續寫入數據的接收端就是流,流機制是Java及C++中的一個重要機制,通過流我們可以自由地控制檔案、記憶體、IO設備等數據的流向。而IO流就是用於處理設備上的數據,如:硬碟、記憶體、鍵盤錄入等。IO流根據處理類型的不同可分為位元組流和字元流,根據流向的不同可分為輸入流和輸出流。
二、位元組流和字元流的區別:
字元流,因為檔案編碼的不同,就有了對字元進行高效操作的字元流對象,它的原理就是基於位元組流讀取位元組時去查了指定的碼錶。它和位元組流的區別有兩點:1.在讀取數據的時候,位元組流讀到一個位元組就返回一個位元組,字元流使用了位元組流讀到一個或多個位元組(一個中文對應的位元組數是兩個,在UTF-8碼錶中是3個位元組)時,先去查指定的編碼表,再將查到的字元返回;2.位元組流可以處理所有類型的數據,如jpg、avi、mp3、wav等等,而字元流只能處理字元數據。所以可以根據處理的檔案不同考慮使用位元組流還是字元流,如果是純文本數據可以優先考慮字元流,否則使用位元組流。
三、IO體系,所具備的基本功能就是讀和寫:
1.字元流
|-- Reader(讀)
|-- Writer(寫)
Reader
|--InputStreamReader
|--FileReader:用於處理檔案的字元讀取流對象
Writer
|--OutputStreamWriter
|--FileWriter:用於處理檔案的字元寫入流對象
其實很容易就可以看出來,IO體系中的子類名後綴絕大部分是父類名稱,而前綴則是體現子類特有功能的名稱。
Reader中常見的方法:
|--int read
讀取一個字元,並返回讀到的這個字元,讀到流的末尾則返回-1。
|--int read(char)
將讀到的字元存入指定的數組中,返回的是讀到的字元個數,
讀到流的末尾則返回-1。
|--close
讀取字元其實用的是window系統的功能,就希望使用完畢後,
進行資源的釋放。
FileReader除了自己的構造函式外沒有特有的方法:
|--用於讀取文本檔案的流對象。
|--用於關聯文本檔案。
|--構造函式FileReader(String fileName)
在讀取流對象初始化時,必須要指定一個被讀取的檔案,
如果該檔案不存在則會發生FileNotFoundException異常。
Writer中常見的方法:
|--write
將一個字元寫入到流中。
|--write(char)
將一個字元數組寫入到流中。
|--writer(String)
將一個字元寫入到流中。
|--flush
刷新流,將流中的數據刷新到目的地中,流還存在。
|--close
關閉資源,在關閉錢會先調用flush, 刷新流中的數據到目的地。
FileWriter,除了自己的構造函式外沒有特有的方法:
|--該類的特點
|--用於處理文本檔案
|--沒有默認的編碼表
|--有臨時緩衝
|--構造函式,在寫入流對象初始化時,必須要有一個存儲數據的目的地。
|--FileWriter(String fileName),該構造器是乾什麼用的呢?
|--調用系統資源
|--在指定位置創建一個檔案,如果該檔案已經存在則被覆蓋。
|--FileWriter(String filename,Boolean append),這構造器的作用是?
當傳入的boolean類型的值為true時,會在指定檔案末尾處進行數據的續寫。
清單1,將文本數據保存到檔案中代碼
private static void test1{
FileWriter fw=null;
try {
//初始化FileWriter對象,指定檔案名稱已經存儲路徑
fw=new FileWriter("D:/test.txt");
fw.write("將字元串寫入流");
//將流中的數據刷新到目的地,流還在
fw.flush;
fw.write("將字元串寫入流");
} catch (IOException e) {
e.printStackTrace;
}finally{
if(fw!=null){
try {
fw.close;
} catch (IOException e1) {
e1.printStackTrace;
}
}
}
}
清單2,讀取一個已有文本檔案,並將文本內容列印出來代碼
private static void test2{
FileReader fr=null;
try {
//初始化FileReader對象,指定檔案路徑
fr=new FileReader("D:/test.txt");
int ch=0;
while((ch=fr.read)!=-1){
//每次讀取一個字元,直到讀到末尾-1為止
System.out.println((char)ch);
}
} catch (IOException e) {
e.printStackTrace;
}finally{
if(fr!=null){
try {
fr.close;
} catch (IOException e1) {
e1.printStackTrace;
}
}
}
}
這樣每讀到一個字元就列印出來,效率很不高,能不能按指定大小讀取完後再列印出來呢?答案是當然可以的。
清單3,讀取一個已有文本檔案,讀完1kb再將其讀到的內容列印出來代碼
private static void test3{
FileReader fr=null;
try {
//初始化FileReader對象,指定檔案路徑
fr=new FileReader("D:/test.txt");
char buf=new char[1024];
int len=0;
while((len=fr.read(buf))!=-1){
//每次讀取1kb大小的字元,直到讀到末尾-1為止
System.out.println(new String(buf,0,len));
}
} catch (IOException e) {
e.printStackTrace;
}finally{
if(fr!=null){
try {
fr.close;
} catch (IOException e1) {
e1.printStackTrace;
}
}
}
}
字元流的緩衝區:
|--緩衝區的出現提高了對流的操作效率。
原理:其實就是將數組進行封裝。
|--對應的對象
|--BufferedWriter
特有方法newLine,跨平台的換行符。
|--BufferedReader
特有方法readLine,一次讀一行,到行標記時,將行標記
之前的字元數據作為字元串返回,讀到末尾返回null。
|--說明
在使用緩衝區對象時,要明確,緩衝的存在是為了增強流
的功能而存在,所以在建立緩衝區對象時,要先有流對象
存在。其實緩衝區內部就是在使用流對象的方法,只不過
加入了數組對數據進行了臨時存儲,為了提高運算元據的
效率。
|--代碼上的體現
|--寫入緩衝區對象
根據前面所說的建立緩衝區時要先有流對象,並將其作為參數傳遞給緩衝區的構造函式
BufferedWriter bufw=new BufferedWriter(new FileWriter(“test.txt”));
bufw.write(“將數據寫入緩衝區”);
bufw.flush;//將緩衝區的數據刷新到目的地
bufw.close;//其實關閉的是被包裝在內部的流對象
|--讀取緩衝區對象
BufferedReader bufr=new BufferedReader(new FileReader(“test.txt”));
String line=null;
while((line=bufr.readLine)!=null){
//每次讀取一行,取出的數據不包含回車符
system.out.println(line);
}
bufr.close;
清單4,使用緩衝區對文本檔案進行拷貝代碼
private static void test4{
BufferedReader bufr=null;
BufferedWriter bufw=null;
try {
bufr=new BufferedReader(new FileReader("D:/a.txt"));
bufw=new BufferedWriter(new FileWriter("D:/b.txt"));
String line=null;
while((line=bufr.readLine)!=null){
bufw.write(line);//每次將一行寫入緩衝區
bufw.flush;//刷新到目的地
}
} catch (IOException e) {
e.printStackTrace;
}finally{
try {
if(bufw!=null){
bufw.close;
}
if(bufr!=null){
bufr.close;
}
} catch (IOException e1) {
e1.printStackTrace;
}
}
}
仔細看可以發現,程式裡面的FileReader對象和FileWriter對象直接new出來且沒有調用close,因為緩衝對象調用了這兩個方法,前面說了,緩衝對象調用的flush和close其實就是關閉被包裝在其內部的流對象。關閉流的先後順序也要注意,如果流之間有依賴關係,則被依賴的流要後關閉。readLine方法原理:其實緩衝區中的該方法,用的還是與緩衝區關聯的流對象的read方法,只不過,每一次讀到一個字元先不進行具體操作,先進行臨時存儲,當讀到回車標記時,將臨時容器中存儲的數據一次性返回。我們可以根據這個原理來自己編寫一個緩衝區對象。
清單5,編寫一個自己的bufferedreader代碼
public class MyBufferedReader {
private Reader reader;
public MyBufferedReader(Reader reader){
this.reader=reader;
}
public String readLine throws IOException{
StringBuilder sb=new StringBuilder;
int ch=0;
while((ch=reader.read)!=-1){
if(ch=='\r'){//空格則繼續
continue;
}else if(ch=='\n'){//每次返回一行
return sb.toString;
}else{
sb.append((char)ch);
}
}
return sb.toString;
}
public void close throws IOException{
//緩衝對象的關閉方法其實就是調用流本身的close
reader.close;
}
}
測試時把清單4的BufferedReader對象替換成MyBufferedReader對象即可。
清單6,測試mybufferedreader代碼
private static void test4{
MyBufferedReader bufr=null;
BufferedWriter bufw=null;
try {
bufr=new MyBufferedReader(new FileReader("D:/a.txt"));
bufw=new BufferedWriter(new FileWriter("D:/b.txt"));
String line=null;
while((line=bufr.readLine)!=null){
bufw.write(line);//每次將一行寫入緩衝區
bufw.flush;//刷新到目的地
}
} catch (IOException e) {
e.printStackTrace;
}finally{
try {
if(bufw!=null){
bufw.close;
}
if(bufr!=null){
bufr.close;
}
} catch (IOException e1) {
e1.printStackTrace;
}
}
}
其實我們自己寫的這個快取對象就是對Reader對象進行了功能的增強,Reader對象每次只能返回一個字元,而增強了功能之後該類就可以每次返回一行字元,也就是設計模式中所說的裝飾模式。