Java 中的 I/O 抽象
Table of Contents
- 前言
- 字節流
- 常用實現
- 字符流
- 常用實現
- 緩沖區
- 各種字節流
- 結語
前言
由於在接觸 Java 之前使用的語言是 Python,所以在轉到 Java 後對 Java 的 I/O 操作各種不習慣。
研究後發現 Java 的 I/O 模型和 Python 的基本上還是一樣的,只是在接口的設計上有些區別,主要是為了符合 OOP 的思想吧。
這篇博客的主要內容便是和 Java I/O 相關的總結。
字節流
和 Python 一樣,Java 中最底層的 I/O 接口處理的是 字節序列, 但是和 Python 中的文件對象一把梭不一樣,Java 將輸入和輸出分別抽象為了兩個對象。
處理輸入流的 InputStream
和處理輸出流的 OutputStream
.
這兩個類都是抽象類,因此具體的和輸入輸出相關的功能將由它們的子類實現,而它們則提供一些基本的接口:
InputStream 提供的部分接口:
方法 作用 abstract int read() 讀取一個字節,碰到輸入流結尾時返回 -1 int read(byte[] b) 將數據讀入提供的字節數組,並返回實際讀入的字節數,或者在碰到輸入流結尾時返回 -1 int read(byte[] b, int off, int len) 將數據讀入提供的字節數組,並返回實際讀入的字節數,或者在碰到輸入流結尾時返回 -1,寫入的範圍由 off 和 len 指定 OutputStream 提供的部分接口:
方法 作用 abstract void write() 寫入一個字節的數據 void write(byte[] b) 寫入一個字節數組的數據 void write(byte[] b, int off, int len) 寫入字節數組的數據,數據範圍由 off 和 len 指定 void flush() 將緩存的數據全部寫入目標
可以看到,抽象方法其實只有 read()
和 write()
, 其他的方法會調用這兩個方法,因此子類只需要提供這兩個抽象方法的實現就可以了。
常用實現
Java 中字節流的具體實現很多,但最常用的應該就是和 文件
FileInputStream 的構造方法:
構造方法 說明 FileInputStream(FileDescriptor fdObj) 根據指定的文件描述符創建輸入流 FileInputStream(File file) 根據指定的文件對象創建輸入流 FileInputStream(String name) 根據指定的文件名稱創建輸入流 FileOutputStream 的構造方法和 FileInputStream 的基本相同,不同的是多了兩個存在布爾標誌的構造方法:
構造方法 說明 FileOutputStream(File file, boolean append) 根據指定的文件對象創建輸出流,布爾標誌指定是否追加 FileOutputStream(String name, boolean append) 根據指定的文件名稱創建輸出流,布爾標誌指定是否追加
在之前學習 C 和 Python 的過程中便了解到 文件描述符 是一個很有用的東西,通過它可以實現一些很有用的功能。
在 Java 中獲取文件描述符可以通過調用 FileInputStream
和 FileOutputStream
對象的 getFD()
方法完成,而標準輸入輸出的文件描述符則需要通過 FileDescriptor
的靜態字段獲取。
字符流
通過字節流可以完成很多 I/O 操作,但是如果連文本文件的處理都通過字節流來完成的話就太麻煩了。因此,為了解決這樣的問題,
字符流便誕生了。
字符流 是對 字節流 的一層封裝,在 Java 中通過 Reader
和 Writer
這兩個抽象類來表示。
和 InputStream 和 OutputStream 一樣,具體的功能將由它們的子類實現,而它們則提供一些基本的接口,這裏列舉出最基本的接口:
Reader:
方法 作用 int read() 讀取單個字符,返回值是該字符的碼點,到達流的末尾就返回 -1 Writer:
方法 作用 void write(int c) 寫入單個字符 void write(String str) 寫入字符串 abstract void flush() 將緩存的數據全部寫入目標
需要註意的是,這裏的 read()
方法和 write()
都不是抽象方法了,因為這兩個方法實際上都是調用內部的 字節流 完成工作,因此,只需要相應的字節流實現基本的功能就足夠了。
常用實現
Java 字符流實現中最常用的應該是 InputStreamReader
和 OutputStreamWriter
了,它們的構造方法如下:
Reader 構造方法 | Writer 構造方法 | 說明 |
---|---|---|
InputStreamReader(InputStream in) | OutputStreamWriter(OutputStream out) | 根據默認編碼創建字符流 |
InputStreamReader(InputStream in, Charset cs) | OutputStreamWriter(OutputStream out, Charset cs) | 根據指定字符集創建字符流 |
InputStreamReader(InputStream in, CharsetDecoder dec) | OutputStreamWriter(OutputStream out, CharsetEncoder dec) | 根據指定字符集解/編碼器創建字符流 |
InputStreamReader(InputStream in, String charsetName) | OutputStreamWriter(OutputStream out, String charsetName) | 根據指定字符集名稱創建字符流 |
同時,針對文件操作,Java 提供了這兩個類的子類 FileReader
和 FileWriter
, 使用這兩個類可以省略手動創建字節流的過程,具體內容可以查看相關文檔。
緩沖區
I/O 操作的一個常識:頻繁的 I/O 操作的效率是很低的,所以我們加一個緩沖區吧!
Java 中我們可以通過 BufferedInputStream
和 BufferedOutputStream
為字節流添加緩沖區,通過 BufferedReader
和 BufferedWriter
為字符流添加緩沖區。
這樣一來,一段經典的代碼就成型了:
// 字節流 -> 字符流 -> 緩沖區
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("example.txt")));
很完美是不是!
各種字節流
雖然說最常用的字節流式文件字節流(大概),但是,輸入輸出的環境是復雜的,處理文件以外,其他的設備如內存、網絡等都可能用到輸入輸出,
而且不同的條件下需要的功能還不一樣。
字符流的需求相對來說較為統一,因此一般情況下 InputStreamReader
和 OutputStreamWriter
完全可以一統天下,但是對於 字節流 來說,Java 提供了各種各樣的實現。
尤其是 FilterInputStream
和 FilterOutputStream
的子類,它們都有以輸出/輸出流作為參數的構造方法,因此,我們可以將不同的 Filter
組裝起來,得到我們想要的功能。
這是除了 字節-字符-緩沖 以外讓我覺得最 Beautiful 的設計,能夠靈活的適應各種需求。
這些 Filter
的使用可以查看官方文檔或者看看《Java 核心技術卷卷二》的 2.1.3 節,這是相當棒的功能。
結語
寫完博客回頭看,感覺質量有點差……
篇幅太少了,很多東西都沒有說清楚,屬於適合自己回顧的博客 @[email protected]
另外,很想吐槽的是:作為面向對象的語言,Java 內置的網絡庫居然沒有將 請求 和 響應 這兩個對象分開!!!
用起來各種不順手……
Java 中的 I/O 抽象