Java 語法 -- IO 流
Java 的 IO 流分為兩大類:
- 位元組流
- 字元流
從超類到實現類,Java IO 流的繼承關係大致如下圖:
InputStream
InputStream 就是 Java 標準庫提供的最基本的位元組輸入流。它位於java.io這個包裡。java.io包提供了所有同步IO的功能。
要特別注意的一點是,InputStream 並不是一個介面,而是一個抽象類,它是所有輸入流的超類。
InputStream 類定義的一個最重要的方法就是 int read(),這個方法會讀取輸入流的下一個位元組,並返回位元組表示的 int 值(0~255)。如果已讀到末尾,返回 -1 表示不能繼續讀取了。
FileInputStream
FileInputStream 是 InputStream 的一個子類。顧名思義,FileInputStream 就是從檔案流中讀取資料。下面的程式碼演示瞭如何完整地讀取一個FileInputStream的所有位元組:
public void readFile() throws IOException { // 建立一個FileInputStream物件: InputStream input = new FileInputStream("src/readme.txt"); for (;;) { int n = input.read(); // 反覆呼叫read()方法,直到返回-1 if (n == -1) { break; } System.out.println(n); // 列印byte的值 } input.close(); // 關閉流 }
InputStream 和 OutputStream 都是通過 close() 方法來關閉流。關閉流就會釋放對應的底層資源。
所有與 IO 操作相關的程式碼都必須正確處理 IOException, 如果讀取過程中發生了 IO 錯誤,InputStream 就沒法正確地關閉,資源也就沒法及時釋放。
利用 Java 7引入的新的 try(resource) 的語法,只需要編寫 try 語句,讓編譯器自動為我們關閉資源。推薦的寫法如下:
public void readFile() throws IOException { try (InputStream input = new FileInputStream("src/readme.txt")) { int n; while ((n = input.read()) != -1) { System.out.println(n); } } // 編譯器在此自動為我們寫入finally並呼叫close() }
OutputStream
和 InputStream 相反,OutputStream 是 Java 標準庫提供的最基本的位元組輸出流。
和InputStream類似,OutputStream也是抽象類,它是所有輸出流的超類。這個抽象類定義的一個最重要的方法就是void write(int b)
和InputStream類似,OutputStream也提供了close()方法關閉輸出流,以便釋放系統資源。要特別注意:OutputStream還提供了一個flush()方法,它的目的是將緩衝區的內容真正輸出到目的地。
FileOutputStream
用 FileOutputStream 可以從檔案獲取輸出流,這是 OutputStream 常用的一個實現類。
以 FileOutputStream 為例,演示如何將若干個位元組寫入檔案流:
public void writeFile() throws IOException {
OutputStream output = new FileOutputStream("out/readme.txt");
output.write(72); // H
output.write(101); // e
output.write(108); // l
output.write(108); // l
output.write(111); // o
output.close();
}
每次寫入一個位元組非常麻煩,更常見的方法是一次性寫入若干位元組。這時,可以用 OutputStream 提供的過載方法void write(byte[])來實現:
public void writeFile() throws IOException {
OutputStream output = new FileOutputStream("out/readme.txt");
output.write("Hello".getBytes("UTF-8")); // Hello
output.close();
}
和InputStream一樣,上述程式碼沒有考慮到在發生異常的情況下如何正確地關閉資源。寫入過程也會經常發生IO錯誤,例如,磁碟已滿,無許可權寫入等等。我們需要用try(resource)來保證OutputStream在無論是否發生IO錯誤的時候都能夠正確地關閉:
public void writeFile() throws IOException {
try (OutputStream output = new FileOutputStream("out/readme.txt")) {
output.write("Hello".getBytes("UTF-8")); // Hello
} // 編譯器在此自動為我們寫入finally並呼叫close()
}
PrintStream
PrintStream 繼承自 FilterOutputStream, FilterOutputStream 又繼承自 OutputStream。
它在OutputStream的介面上,額外提供了一些寫入各種資料型別的方法:
寫入int:print(int)
寫入boolean:print(boolean)
寫入String:print(String)
寫入Object:print(Object),實際上相當於print(object.toString())
以及對應的一組println()方法,它會自動加上換行符。
我們經常使用的System.out.println()實際上就是使用PrintStream列印各種資料。其中,System.out是系統預設提供的PrintStream,表示標準輸出:
System.out.print(12345); // 輸出12345
System.out.print(new Object()); // 輸出類似java.lang.Object@3c7a835a
System.out.println("Hello"); // 輸出Hello並換行
System.err是系統預設提供的標準錯誤輸出。
PrintStream和OutputStream相比,除了添加了一組print()/println()方法,可以列印各種資料型別,比較方便外,它還有一個額外的優點,就是不會丟擲IOException,這樣我們在編寫程式碼的時候,就不必捕獲IOException。
序列化與反序列化
序列化是指把一個Java物件變成二進位制內容,本質上就是一個byte[]陣列。
為什麼要把Java物件序列化呢?因為序列化後可以把byte[]儲存到檔案中,或者把byte[]通過網路傳輸到遠端,這樣,就相當於把Java物件儲存到檔案或者通過網路傳輸出去了。
有序列化,就有反序列化,即把一個二進位制內容(也就是byte[]陣列)變回Java物件。有了反序列化,儲存到檔案中的byte[]陣列又可以“變回”Java物件,或者從網路上讀取byte[]並把它“變回”Java物件。
ObjectOutputStream
把一個 Java 物件變為 byte[] 陣列,需要使用 ObjectOutputStream。它負責把一個 Java 物件寫入一個位元組流:
ObjectInputStream
和ObjectOutputStream相反,ObjectInputStream負責從一個位元組流讀取Java物件:
try (ObjectInputStream input = new ObjectInputStream(...)) {
int n = input.readInt();
String s = input.readUTF();
Double d = (Double) input.readObject();
}
除了能讀取基本型別和String型別外,呼叫readObject()可以直接返回一個Object物件。要把它變成一個特定型別,必須強制轉型。
Java的序列化機制僅適用於Java,如果需要與其它語言交換資料,必須使用通用的序列化方法,例如JSON。
Reader 介面
Reader是Java的IO庫提供的另一個輸入流介面。和InputStream的區別是,InputStream是一個位元組流,即以byte為單位讀取,而Reader是一個字元流,即以char為單位讀取:
java.io.Reader是所有字元輸入流的超類,它最主要的方法是:int read()
這個方法讀取字元流的下一個字元,並返回字元表示的int,範圍是0~65535。如果已讀到末尾,返回-1。
BufferedReader
BufferedReader 是緩衝字元輸入流。它繼承於Reader。
BufferedReader 的作用是為其他字元輸入流新增一些緩衝功能。
提供了讀取一行的功能:readLine()
LineNumberReader
LineNumberReader 繼承自 BufferedReader,並且增加了下面兩個功能:
- 獲取行號:getLineNumber()
- 設定行號:setLineNumber()
InputStreamReader
InputStreamReader : 是位元組流與字元流之間的橋樑,能將位元組流輸出為字元流,並且能為位元組流指定字符集,可輸出一個個的字元.
FileReader
FileReader 繼承自 InputStreamReader
它可以開啟檔案並獲取Reader。下面的程式碼演示瞭如何完整地讀取一個FileReader的所有字元:
public void readFile() throws IOException {
// 建立一個FileReader物件:
Reader reader = new FileReader("src/readme.txt");
for (;;) {
int n = reader.read(); // 反覆呼叫read()方法,直到返回-1
if (n == -1) {
break;
}
System.out.println((char)n); // 列印char
}
reader.close(); // 關閉流
}
StringReader
StringReader是介面 Reader 的一個實現類,其用法是讀取一個String字串。
Writer 介面
Reader是帶編碼轉換器的InputStream,它把byte轉換為char,而Writer就是帶編碼轉換器的OutputStream,它把char轉換為byte並輸出。
Writer和OutputStream的區別如下:
Writer是所有字元輸出流的超類,它提供的方法主要有:
- 寫入一個字元(0~65535):void write(int c);
- 寫入字元陣列的所有字元:void write(char[] c);
- 寫入String表示的所有字元:void write(String s)。
BufferedWriter
BufferedWriter 為帶有預設緩衝的字元輸出流。
主要方法:
-
void write(char ch);//寫入單個字元。
-
void write(char []cbuf,int off,int len)//寫入字元資料的某一部分。
-
void write(String s,int off,int len)//寫入字串的某一部分。
-
void newLine()//寫入一個行分隔符。
-
void flush();//重新整理該流中的緩衝。將緩衝資料寫到目的檔案中去。
-
void close();//關閉此流,再關閉前會先重新整理他。
OutputStreamWriter
整個IO包實際上分為位元組流和字元流,但是除了這兩個流之外,還存在一組位元組流-字元流的轉換類。
OutputStreamWriter:是Writer的子類,將輸出的字元流變為位元組流,即將一個字元流的輸出物件變為位元組流輸出物件。
InputStreamReader:是Reader的子類,將輸入的位元組流變為字元流,即將一個位元組流的輸入物件變為字元流的輸入物件。
如果以檔案操作為例,則記憶體中的字元資料需要通過OutputStreamWriter變為位元組流才能儲存在檔案中,讀取時需要將讀入的位元組流通過InputStreamReader變為字元流。過程如下:
寫入資料-->記憶體中的字元資料-->字元流-->OutputStreamWriter-->位元組流-->網路傳輸(或檔案儲存)
讀取資料<--記憶體中的字元資料<--字元流<--InputStreamReader<--位元組流<--網路傳輸(或檔案儲存)
可以清楚地發現,不管如何操作,最終全部是以位元組的形式儲存在檔案中或者進行網路傳輸。
FileWriter
FileWriter 類從 OutputStreamWriter 類繼承而來。該類按字元向流中寫入資料。
它的使用方法和FileReader類似:
try (Writer writer = new FileWriter("readme.txt", StandardCharsets.UTF_8)) {
writer.write('H'); // 寫入單個字元
writer.write("Hello".toCharArray()); // 寫入char[]
writer.write("Hello"); // 寫入String
}
PrintWriter
PrintStream最終輸出的總是byte資料,而PrintWriter則是擴充套件了Writer介面,它的print()/println()方法最終輸出的是char資料。兩者的使用方法幾乎是一模一樣的:
public class Main {
public static void main(String[] args) {
StringWriter buffer = new StringWriter();
try (PrintWriter pw = new PrintWriter(buffer)) {
pw.println("Hello");
pw.println(12345);
pw.println(true);
}
System.out.println(buffer.toString());
}
}
StringWriter
StringWriter 是介面 Writer 的一個實現類,主要作用是寫入一個字串。
每天學習一點點,每天進步一點點。