Java 中日常使用的 IO 流總結
在 Java IO 流體系中提供了近 40 個類,這些類看起來非常亂又沒有規律,所以我之前一直很牴觸 IO 流,我覺得太亂了。但是在寫程式碼的時候呢,又會經常要去使用,而且經常又用錯...
所以這次花時間去重新複習了一遍,經過總結髮現我們日常使用到的 IO 流其實就那幾個,就能滿足我們的日常需求,在這裡寫一些總結,方便日後複習。
節點流
所有節點流都是直接以物理 IO 節點作為構造器引數的,例如 FileInputStream fis = new FileInputStream("obj.txt");
首先是最原始的 4 個節點流介面:InputStream
、OutputStream
、Reader
Writer
。
以及他們的常用的直接實現類:FileInputStream
、FileOutputStream
、FileReader
、FileWriter
。
這 4 個是最常用的,不多介紹,這裡舉個檔案複製的例子:
import java.io.*; /** * 將 aa.txt 的內容 複製到 bb.txt */ public class FileCopyDemo { public static void main(String[] args) { try { // 建立流 FileInputStream fis = new FileInputStream("aa.txt"); FileOutputStream fos = new FileOutputStream("bb.txt"); byte[] buff = new byte[256]; int hasRead = 0; // 讀取 fis 的內容到 buff中,每次最多讀取 256 位元組 while ((hasRead = fis.read(buff)) > 0){ fos.write(buff); } //關閉流 fos.close(); fis.close(); } catch (IOException e) { e.printStackTrace(); } } }
位元組流和字元流的功能類似,只不過字元流只能操作字元、文字檔案,而不能操作圖片、音訊等檔案。
字元流的檔案複製案例:
import java.io.*; /** * 字元流的檔案複製 */ public class Demo2 { public static void main(String[] args) { try { //建立字元流 FileReader fis = new FileReader("aa.txt"); FileWriter fos = new FileWriter("bb.txt"); //操作的是字元 char[] buff = new char[256]; int hasRead = 0; while ((hasRead = fis.read(buff)) > 0){ fos.write(buff); } //關閉流 fos.close(); fis.close(); } catch (IOException e) { e.printStackTrace(); } } }
節點流的基本概念模型如下圖。是每一次獲取資料是通過一個節點去獲取。
處理流
對比節點流,處理流是 ”嫁接“ 在已存在的流的基礎之上,允許應用程式採用相同的程式碼、透明的方式來訪問不同的輸入/輸出裝置的資料流。
通過使用處理流,Java 程式無須理會輸入/輸出節點是磁碟、網路還是其他裝置,程式只要將這些節點流包裝成處理流,就可以使用相同的輸入/輸出程式碼來讀寫不同 IO 裝置的資料。
使用處理流的典型思路是,使用處理流來包裝節點流,程式通過處理流來執行輸入/輸出功能,讓節點流與底層的 I/O 裝置互動。
識別處理流的方法很簡單,只要流的構造器引數不是一個物理節點,而是一個已經存在的流,這種流就一定是節點流。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
/**
* 使用 PrintStream 處理流來包裝 OutputStream
*/
public class PrintStreamTest {
public static void main(String[] args) {
try {
// 建立位元組輸出流
FileOutputStream fos = new FileOutputStream("aa.txt");
// 包裝成處理流
PrintStream ps = new PrintStream(fos);
//下面這種方式效率更高
// PrintWriter ps = new PrintWriter(new BufferedWriter(new FileWriter("aa.txt")));
// 使用 PrintStream 執行輸出
ps.println("這個會輸出到檔案");
// 直接將這個物件輸出到檔案
ps.println(new PrintStreamTest());
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面使用到一個處理流 PrintStream,它的輸出功能非常強大,標準輸出 System.out 的型別就是 PrintStream。
通常如果需要輸出文字內容,都應該包裝成 PrintStream 後進行輸出。
與之對應的還有一個處理字元輸出流的處理流 PrintWriter。
常用的處理流還有 BufferedWriter
、BufferedReader
,這兩個也叫緩衝流,後面的例子會使用到。
轉換流
IO 體系中提供了兩個轉換流:
-
InputStreamReader
:將直接輸入流轉換成字元輸入流 -
OutputStreamWriter
:將位元組輸出流轉換成字元輸出流
IO 轉換流只有 位元組流 ----> 字元流
【為什麼】
因為位元組流適用範圍更廣,但字元流操作更方便。如果一個流已經是字元流了,是一個用起來更方便的流,為什麼要轉換成位元組流呢?反正,如果一個位元組流,但可以明確知道這個流的內容是文字,那麼將它轉換成字元流來處理會更方便一些。
所以 Java 只提供了將
位元組流
轉換成字元流
的轉換流。
下面舉個例子。Java 使用 System.in 代表標準輸入流,即鍵盤輸入。但這個標準輸入流是 InputStream 類的例項,使用起來不方便,而且鍵盤輸入的內容都是文字內容,所以可以使用 InputStreamReader 將其轉換成字元輸入流。
import java.io.*;
/**
* 轉換流使用,將鍵盤輸入的內容寫入檔案 bb.txt
*/
public class BufferTest {
public static void main(String[] args) {
try {
// 將 System.in 物件轉行成 Reader 物件
InputStreamReader reader = new InputStreamReader(System.in);
// 將普通的 Reader 包裝成處理流 BufferedReader
BufferedReader br = new BufferedReader(reader);
// 建立輸出流,並轉換成字元流 Writer
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("bb.txt",true));
String line= null;
// 採用迴圈讀取鍵盤輸入的內容
while ((line = br.readLine()) != null) {
if (line.equals("quit")){
System.exit(1);
}
// 寫入輸出流
writer.write(line);
// 換行
writer.write('\n');
// 重新整理輸出流,寫入檔案
writer.flush();
System.out.println("輸入的內容為:" + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
這裡還用到了 BufferedReader,將普通的 Reader 再次包裝成處理流 BufferedReader,利用它的 readLine() 方法一次可以讀取一行內容。
【提示】
BufferedReader 具有一個 readLine() 方法,可以非常方便地一次讀入一行內容,所以經常把讀取文字內容的輸入流包裝成 BufferedReader ,用來方便地讀取輸入流的文字內容。
緩衝流
緩衝流是從字元流中讀取/寫入文字,緩衝各個字元,從而實現字元、陣列和行的高效讀取。
可以指定緩衝區的大小,或者可使用預設的大小。大多數情況下,預設值就足夠大了。
BufferedReader
通常,Reader 所作的每個讀取請求都會導致對底層字元或位元組流進行相應的讀取請求。因此,建議用 BufferedReader 包裝所有其 read() 操作可能開銷很高的 Reader(如 FileReader 和 InputStreamReader)。
例如, 將緩衝指定檔案的輸入:
BufferedReader in = new BufferedReader(new FileReader("foo.in"));
如果沒有緩衝,則每次呼叫 read() 或 readLine() 都會導致從檔案中讀取位元組,並將其轉換為字元後返回,而這是極其低效的。
BufferedWriter
該類提供了 newLine() 方法,它使用平臺自己的行分隔符概念,此概念由系統屬性 line.separator
定義。並非所有平臺都使用新行符 ('\n') 來終止各行。因此呼叫此方法來終止每個輸出行要優於直接寫入新行符。
通常 Writer 將其輸出立即傳送到底層字元或位元組流。除非要求提示輸出,否則建議用 BufferedWriter 包裝所有其 write() 操作可能開銷很高的 Writer(如 FileWriters 和 OutputStreamWriters)。例如,將緩衝 PrintWriter 對檔案的輸出。
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out")));
如果沒有緩衝,則每次呼叫 print() 方法會導致將字元轉換為位元組,然後立即寫入到檔案,而這是極其低效的。
所以前面處理流的例子中,用第二種方式建立處理流效率更高。
下面例子將鍵盤輸入的內容輸出到檔案:
import java.io.*;
/**
* 緩衝流
*/
public class BufferedWriterTest {
public static void main(String[] args) {
try {
//輸出流
FileOutputStream fos = new FileOutputStream("aa.txt", true);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos));
//建立輸入流, 從鍵盤輸入
InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(reader);
String line = null;
while (!(line = br.readLine()).equals("quit")) {
writer.write(line);
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}