Java IO體系
個人覺得可以用“字節流操作類和字符流操作類組成了Java IO體系”來高度概括Java IO體系。
借用幾張網絡圖片來說明(圖片來自 http://blog.csdn.net/zhangerqing/article/details/8466532 )
- 基於字節的IO操作
![]()
|
![]()
|
- 基於字符的IO操作
![]()
|
![]()
|
從上圖可以看到,整個Java IO體系都是基於字符流(InputStream/OutputStream) 和 字節流(Reader/Writer)作為基類,根據不同的數據載體或功能派生出來的。
IO常用類
- 文件流:FileInputStream/FileOutputStream, FileReader/FileWriter
這四個類是專門操作文件流的,用法高度相似,區別在於前面兩個是操作字節流,后面兩個是操作字符流。它們都會直接操作文件流,直接與OS底層交互。因此他們也被稱為節點流。
注意使用這幾個流的對象之后,需要關閉流對象,因為java垃圾回收器不會主動回收。不過在Java7之后,可以在 try() 括號中打開流,最后程序會自動關閉流對象,不再需要顯示地close。
下面演示這四個流對象的基本用法,
1 package io; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.FileReader; 7 import java.io.FileWriter; 8 import java.io.IOException; 9 10 public class TestIO { 11 public static void FileInputStreamTest() throws IOException { 12 FileInputStream fis = new FileInputStream("tmp2.txt"); 13 byte[] buf = new byte[1024]; 14 int hasRead = 0; 15 16 //read()返回的是單個字節數據(字節數據可以直接專程int類型),但是read(buf)返回的是讀取到的字節數,真正的數據保存在buf中 17 while ((hasRead = fis.read(buf)) > 0) { 18 //每次最多將1024個字節轉換成字符串,這里tmp2.txt中的字符小於1024,所以一次就讀完了 19 //循環次數 = 文件字符數 除以 buf長度 20 System.out.println(new String(buf, 0 ,hasRead)); 21 /* 22 * 將字節強制轉換成字符后逐個輸出,能實現和上面一樣的效果。但是如果源文件是中文的話可能會亂碼 23 24 for (byte b : buf) { 25 char ch = (char)b; 26 if (ch != '\r') 27 System.out.print(ch); 28 } 29 */ 30 } 31 //在finally塊里close更安全 32 fis.close(); 33 } 34 35 public static void FileReaderTest() throws IOException { 36 37 try ( 38 // 在try() 中打開的文件, JVM會自動關閉 39 FileReader fr = new FileReader("tmp2.txt")) { 40 char[] buf = new char[32]; 41 int hasRead = 0; 42 // 每個char都占兩個字節,每個字符或者漢字都是占2個字節,因此無論buf長度為多少,總是能讀取中文字符長度的整數倍,不會亂碼 43 while ((hasRead = fr.read(buf)) > 0) { 44 // 如果buf的長度大於文件每行的長度,就可以完整輸出每行,否則會斷行。 45 // 循環次數 = 文件字符數 除以 buf長度 46 System.out.println(new String(buf, 0, hasRead)); 47 // 跟上面效果一樣 48 // System.out.println(buf); 49 } 50 } catch (IOException ex) { 51 ex.printStackTrace(); 52 } 53 } 54 55 public static void FileOutputStreamTest() throws FileNotFoundException, IOException { 56 try ( 57 //在try()中打開文件會在結尾自動關閉 58 FileInputStream fis = new FileInputStream("tmp2.txt"); 59 FileOutputStream fos = new FileOutputStream("tmp3.txt"); 60 ) { 61 byte[] buf = new byte[4]; 62 int hasRead = 0; 63 while ((hasRead = fis.read(buf)) > 0) { 64 //每讀取一次就寫一次,讀多少就寫多少 65 fos.write(buf, 0, hasRead); 66 } 67 System.out.println("write success"); 68 } catch (IOException e) { 69 e.printStackTrace(); 70 } 71 } 72 73 public static void FileWriterTest() throws IOException { 74 try (FileWriter fw = new FileWriter("tmp4.txt")) { 75 fw.write("天王蓋地虎\r\n"); 76 fw.write("寶塔鎮河妖\r\n"); 77 } catch (IOException e) { 78 e.printStackTrace(); 79 } 80 } 81 public static void main(String[] args) throws IOException { 82 //FileInputStreamTest(); 83 //FileReaderTest(); 84 //FileOutputStreamTest(); 85 FileWriterTest(); 86 } 87 }
- 包裝流:PrintStream/PrintWriter/Scanner
PrintStream可以封裝(包裝)直接與文件交互的節點流對象OutputStream, 使得編程人員可以忽略設備底層的差異,進行一致的IO操作。因此這種流也稱為處理流或者包裝流。
PrintWriter除了可以包裝字節流OutputStream之外,還能包裝字符流Writer
Scanner可以包裝鍵盤輸入,方便地將鍵盤輸入的內容轉換成我們想要的數據類型。
- 字符串流:StringReader/StringWriter
這兩個操作的是專門操作String字符串的流,其中StringReader能從String中方便地讀取數據並保存到char數組,而StringWriter則將字符串類型的數據寫入到StringBuffer中(因為String不可寫)。
- 轉換流:InputStreamReader/OutputStreamReader
這兩個類可以將字節流轉換成字符流,被稱為字節流與字符流之間的橋梁。我們經常在讀取鍵盤輸入(System.in)或網絡通信的時候,需要使用這兩個類
- 緩沖流:BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream
Oracle官方的描述:
Most of the examples we've seen so far use unbuffered I/O. This means each read or write request is handled directly by the underlying OS. This can make a program much less efficient.
Buffered input streams read data from a memory area known as a buffer; the native input API is called only when the buffer is empty. Similarly, buffered output streams write data to a buffer, and the native output API is called only when the buffer is full.
即,
沒有經過Buffered處理的IO, 意味着每一次讀和寫的請求都會由OS底層直接處理,這會導致非常低效的問題。
經過Buffered處理過的輸入流將會從一個buffer內存區域讀取數據,本地API只會在buffer空了之后才會被調用(可能一次調用會填充很多數據進buffer)。
經過Buffered處理過的輸出流將會把數據寫入到buffer中,本地API只會在buffer滿了之后才會被調用。
BufferedReader/BufferedWriter可以將字符流(Reader)包裝成緩沖流,這是最常見用的做法。
另外,BufferedReader提供一個readLine()可以方便地讀取一行,而FileInputStream和FileReader只能讀取一個字節或者一個字符,
因此BufferedReader也被稱為行讀取器
下面演示上面提到的常見類,
1 package io; 2 3 import java.io.BufferedReader; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.FileOutputStream; 7 import java.io.FileReader; 8 import java.io.IOException; 9 import java.io.InputStreamReader; 10 import java.io.PrintStream; 11 import java.io.PushbackReader; 12 import java.io.StringReader; 13 import java.io.StringWriter; 14 15 public class TestIO { 16 public static void printStream() throws FileNotFoundException, IOException { 17 try ( 18 FileOutputStream fos = new FileOutputStream("tmp.txt"); 19 PrintStream ps = new PrintStream(fos)) { 20 ps.println("普通字符串\n"); 21 //輸出對象 22 ps.println(new TestIO()); 23 } catch (IOException e) { 24 e.printStackTrace(); 25 } 26 System.out.println("輸出完成"); 27 28 } 29 public static void stringNode() throws IOException { 30 String str = "天王蓋地虎\n" 31 + "寶塔鎮河妖\n"; 32 char[] buf = new char[32]; 33 int hasRead = 0; 34 //StringReader將以String字符串為節點讀取數據 35 try (StringReader sr = new StringReader(str)) { 36 while ((hasRead = sr.read(buf)) > 0) { 37 System.out.print(new String(buf, 0, hasRead)); 38 } 39 } catch (IOException e) { 40 e.printStackTrace(); 41 } 42 43 //由於String是一個不可變類,因此創建StringWriter時,實際上是以一個StringBuffer作為輸出節點 44 try (StringWriter sw = new StringWriter()) { 45 sw.write("黑夜給了我黑色的眼睛\n"); 46 sw.write("我卻用它尋找光明\n"); 47 //toString()返回sw節點內的數據 48 System.out.println(sw.toString()); 49 } catch (IOException e) { 50 e.printStackTrace(); 51 } 52 } 53 54 public static void keyIn() throws IOException { 55 try ( 56 //InputStreamReader是從byte轉成char的橋梁 57 InputStreamReader reader = new InputStreamReader(System.in); 58 //BufferedReader(Reader in)是char類型輸入的包裝類 59 BufferedReader br = new BufferedReader(reader); 60 ) { 61 String line = null; 62 while ((line = br.readLine()) != null) { 63 if (line.equals("exit")) { 64 //System.exit(1); 65 break; 66 } 67 System.out.println(line); 68 } 69 } catch (IOException e) { 70 e.printStackTrace(); 71 } 72 } 73 74 public static void pushback() throws FileNotFoundException, IOException { 75 try (PushbackReader pr = new PushbackReader(new FileReader("C:/PROJECT/JavaBasic/PROJECT_JavaBasic/src/io/TestIO.java"),64)) { 76 char[] buf = new char[32]; 77 String lastContent = ""; 78 int hasRead = 0; 79 while ((hasRead = pr.read(buf)) > 0) { 80 String content = new String(buf, 0, hasRead); 81 int targetIndex = 0; 82 if ((targetIndex = (lastContent + content).indexOf("targetIndex = (lastContent + content)")) > 0) { 83 pr.unread((lastContent + content).toCharArray()); 84 if (targetIndex > 32) { 85 buf = new char[targetIndex]; 86 } 87 pr.read(buf , 0 , targetIndex); 88 System.out.println(new String(buf, 0 , targetIndex)); 89 System.exit(0); 90 } else { 91 System.out.println(lastContent); 92 lastContent = content; 93 } 94 } 95 } catch (IOException e) { 96 e.printStackTrace(); 97 } 98 } 99 100 public static void main(String[] args) throws IOException { 101 printStream(); 102 //stringNode(); 103 //keyIn(); 104 //pushback(); 105 } 106 }
總結上面幾種流的應用場景:
- FileInputStream/FileOutputStream 需要逐個字節處理原始二進制流的時候使用,效率低下
- FileReader/FileWriter 需要組個字符處理的時候使用
- StringReader/StringWriter 需要處理字符串的時候,可以將字符串保存為字符數組
- PrintStream/PrintWriter 用來包裝FileOutputStream 對象,方便直接將String字符串寫入文件
- Scanner 用來包裝System.in流,很方便地將輸入的String字符串轉換成需要的數據類型
- InputStreamReader/OutputStreamReader , 字節和字符的轉換橋梁,在網絡通信或者處理鍵盤輸入的時候用
- BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream , 緩沖流用來包裝字節流后者字符流,提升IO性能,BufferedReader還可以方便地讀取一行,簡化編程。