在Java API中,可以從其中讀入一個字節序列的對象稱作輸入流,而可以向其中寫入一個字節序列的對象稱作輸出流。這些字節序列的來源地和目的地可以是文件,而且通常都是文件,但是也可以是網絡連接,甚至是內存塊。編程語言的I/O類庫中使用流這個抽象概念,它代表任何有能力產出數據的數據源對象或者是有能力接收數據的接收端對象。
簡單的理解:流是數據和數據處理過程的統稱。
流操作關心三部分內容:數據源、目標以及過程。
這些數據源包括:
1)字節數組
2)String對象
3)文件
4)“管道”,工作方式與實際管道相似,即,從一段輸入,從另一端輸出。
5)一個由其他種類的流組成的序列,以便我們可以將它們收集合並到一個流內。
6)其他數據源,如Internet連接等。
1. 按流的方向:輸入流和輸出流
可以從其中讀入一個字節序列的對象稱作輸入流,而可以向其中寫入一個字節序列的對象稱作輸出流。入和出都是相對於程序本身來說的(不是相當於流本身,因為程序中的“入”,是將流中的數據讀取到程序中,對流來說此時是“出”),比如,在程序中使用一個文件保存數據,進行的操作是向流中寫入數據保存到文件,此時的流是輸出流;從流中讀取數據到程序的流稱為輸入流。
上面的概念中也反映了該類流的特點:對輸入流只能進行讀操作,對輸出流只能進行寫操作。
1. 按數據單位:字節流和字符流
在電腦磁盤上,所有的數據都是以字節的形式傳輸和保存,包括圖片、視頻、文檔等數據。一個字節占用8位二進制(8bit),而一個字符根據編碼方式的不同,占用的存儲空間是不同的。有句話很適合現在:基本的才是萬能的。所以字節流能夠處理任何類型的數據,而字符流只能處理字符類型的數據。
2. 按功能:節點流和過濾流
節點流是使用原始的流類進行的操作,而過濾流是在節點流的基礎上進行修飾以獲得更多的功能,這裡使用了裝飾器模式。
Reader和Writer的層次結構如下圖:
Java類庫中的I/O類分成輸入和輸出兩部分,可以在JDK的文檔裡的類層次結構中查看到。通過繼承,任何自InputStream或Reader派生而來的類都含有名為read()的基本方法,用於讀取單個字節或字節數組。同樣,任何自OutputStream或Writer類派生未來的類都含有名為write()的基本方法,用於寫單個字節或者字節數組。但是,我們通常不會用到這些方法,他們之所以存在是因為別的類可以使用它們,以便提供更有用的接口。因此,我們很少使用單一的類來創建流對象,而是通過疊合多個對象來提供所期望的功能(這就是裝飾器設計模式)。實際上,Java“流”類庫讓人迷惑的主要原因就在於:創建單一的結果流,卻需要創建多個對象。
FilterInputStream和FilterOutputStream是用來提供裝飾器類接口以控制特定輸入流額輸出流的兩個類,他們的名字不是很直觀。FilterInputStream和FilterOutputStream分別自I/O類庫中的積累InputStream和OutputStream派生而來,這兩個類是裝飾器的必要條件(以便能為所有正在被修飾的對象提供通用接口)。
FilterInputStream類能夠完成兩件完全不同的事情。其中,DataInputStream允許我們讀取不同的基本類型數據以及String對象(所有方法都以“read”開頭,例如readByte()、readFloat()等等)。搭配相應的DataInputStream,我們就可以通過數據“流”將基本類型的數據從一個地方遷移到另一個地方。
其他FilterInputStream類則是在內部修改InputStream的行為方式:是否緩沖,是否保留它所讀過的行(允許我們查詢行數或設置行數),以及是否把單一字符推回輸入流等等。最後兩個類看起來更像是為了創建一個編譯器(它們被添加進來可能是為了對“用Java構建編譯器”實驗提供支持),因此我們在一般編程中不會用到它們。
我們幾乎每次都要對輸入 進行緩沖——不管我們正在連接的是什麼I/O設備,所以,I/O類庫把無緩沖輸入(而不是緩沖輸入)作為特殊情況(或只是方法調用)就顯得更加合理了。
InputStream表示字節輸入流的所有類的超類,
FileInputStream 從文件系統中的某個文件中獲得輸入字節。
ByteArrayInputStream 包含一個內部緩沖區,該緩沖區包含從流中讀取的字節。內部計數器跟蹤read
方法要提供的下一個字節。關閉ByteArrayInputStream 無效。此類中的方法在關閉此流後仍可被調用,而不會產生任何IOException。
PipedInputStream管道輸入流應該連接到管道輸出流;管道輸入流提供要寫入管道輸出流的所有數據字節。通常,數據由某個線程從PipedInputStream
對象讀取,並由其他線程將其寫入到相應的PipedOutputStream
。不建議對這兩個對象嘗試使用單個線程,因為這樣可能死鎖線程。管道輸入流包含一個緩沖區,可在緩沖區限定的范圍內將讀操作和寫操作分離開。如果向連接管道輸出流提供數據字節的線程不再存在,則認為該管道已損壞。
FilterInputStream 包含其他一些輸入流,它將這些流用作其基本數據源,它可以直接傳輸數據或提供一些額外的功能。
ObjectInputStream 對以前使用 ObjectOutputStream 寫入的基本數據和對象進行反序列化。
SequenceInputStream 表示其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到到達文件末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾為止。
BufferedInputStream 為另一個輸入流添加一些功能,即緩沖輸入以及支持mark
和reset
方法的能力。在創建BufferedInputStream
時,會創建一個內部緩沖區數組。在讀取或跳過流中的字節時,可根據需要從包含的輸入流再次填充該內部緩沖區,一次填充多個字節。mark
操作記錄輸入流中的某個點,reset
操作使得在從包含的輸入流中獲取新字節之前,再次讀取自最後一次mark
操作後讀取的所有字節。
DataInputStream數據輸入流允許應用程序以與機器無關方式從底層輸入流中讀取基本 Java 數據類型。應用程序可以使用數據輸出流寫入稍後由數據輸入流讀取的數據。
PushbackInputStream 為另一個輸入流添加性能,即“推回 (push back)”或“取消讀取 (unread)”一個字節的能力。
OutputStream此抽象類是表示輸出字節流的所有類的超類。
ByteArrayOutputStream此類實現了一個輸出流,其中的數據被寫入一個 byte 數組。緩沖區會隨著數據的不斷寫入而自動增長。可使用toByteArray()
和toString()
獲取數據。關閉ByteArrayOutputStream 無效。
FileOutputStream文件輸出流是用於將數據寫入 File
或FileDescriptor
的輸出流。
FilterOutputStream此類是過濾輸出流的所有類的超類。
ObjectOutputStream 將 Java 對象的基本數據類型和圖形寫入 OutputStream。
PipedOutputStream可以將管道輸出流連接到管道輸入流來創建通信管道。管道輸出流是管道的發送端。
BufferedOutputStream該類實現緩沖的輸出流。通過設置這種輸出流,應用程序就可以將各個字節寫入底層輸出流中,而不必針對每次字節寫入調用底層系統。
DataOutputStream數據輸出流允許應用程序以適當方式將基本 Java 數據類型寫入輸出流中。然後,應用程序可以使用數據輸入流將數據讀入。
Reader 用於讀取字符流的抽象類。
BufferedReader從字符輸入流中讀取文本,緩沖各個字符,從而實現字符、數組和行的高效讀取。
LineNumberReader跟蹤行號的緩沖字符輸入流。此類定義了方法setLineNumber(int) 和 getLineNumber(),它們可分別用於設置和獲取當前行號。
CharArrayReader此類實現一個可用作字符輸入流的字符緩沖區。
InputStreamReader 是字節流通向字符流的橋梁:它使用指定的 charset 讀取字節並將其解碼為字符。它使用的字符集可以由名稱指定或顯式給定,或者可以接受平台默認的字符集。
FileReader用來讀取字符文件的便捷類。此類的構造方法假定默認字符編碼和默認字節緩沖區大小都是適當的。
StringReader其源為一個字符串的字符流。
PipedReader傳送的字符輸入流。
FilterReader用於讀取已過濾的字符流的抽象類。
PushbackReader允許將字符推回到流的字符流 reader。
Writer寫入字符流的抽象類。
BufferedWriter將文本寫入字符輸出流,緩沖各個字符,從而提供單個字符、數組和字符串的高效寫入。
CharArrayWriter此類實現一個可用作Writer 的字符緩沖區。緩沖區會隨向流中寫入數據而自動增長。可使用 toCharArray() 和 toString() 獲取數據。在此類上調用 close() 無效。
OutputStreamWriter是字符流通向字節流的橋梁:可使用指定的 charset 將要寫入流中的字符編碼成字節。它使用的字符集可以由名稱指定或顯式給定,否則將接受平台默認的字符集。
FileWriter用來寫入字符文件的便捷類。
PipedWriter傳送的字符輸出流。
PrintWriter向文本輸出流打印對象的格式化表示形式。此類實現在 PrintStream 中的所有 print 方法。它不包含用於寫入原始字節的方法,對於這些字節,程序應該使用未編碼的字節流進行寫入。
StringWriter一個字符流,可以用其回收在字符串緩沖區中的輸出來構造字符串。關閉 StringWriter 無效。
FilterWriter用於寫入已過濾的字符流的抽象類。
1.緩沖輸入文件
public static String read(String filename) throws IOException{ BufferedReader in = new BufferedReader(new FileReader(filename)); String s; StringBuilder sb = new StringBuilder(); while((s = in.readLine())!=null){ sb.append(s + "\n"); } in.close(); return sb.toString(); }
2.從內存輸入
public static void memoryInput(String filename) throws IOException{ StringReader in = new StringReader(BufferedInputFile.read(filename)); int c; while((c = in.read())!=-1){ System.out.print((char)c); } }
3.格式化的內存輸入
public static void formatMemoryInput(String filename) throws IOException{ try{ DataInputStream in = new DataInputStream(new ByteArrayInputStream(BufferedInputFile.read(filename).getBytes())); while(true){ System.out.print((char)in.readByte()); } }catch(IOException e){ System.out.println("End of stream"); } }
4.一次一個字節的讀取文件
public static void testEOF(String filename) throws IOException{ DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename))); while(in.available() != 0){ System.out.print((char)in.readByte()); } in.close(); }
5.基本的文件輸出
public static void basicFileOut(String filename , String fileout) throws IOException{ BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read(filename))); PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileout))); int lineCount = 1; String s; while((s = in.readLine())!=null){ out.println(lineCount++ + ":" + s); } out.close(); System.out.println(BufferedInputFile.read(fileout)); }
6. 文本文件輸出的快捷方式