1. 程式人生 > >Java 中的 I/O 抽象

Java 中的 I/O 抽象

描述 pad 經典 output tde 一個 write 標準 append

Table of Contents

  1. 前言
  2. 字節流
    1. 常用實現
  3. 字符流
    1. 常用實現
  4. 緩沖區
  5. 各種字節流
  6. 結語

前言

由於在接觸 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 中獲取文件描述符可以通過調用 FileInputStreamFileOutputStream 對象的 getFD() 方法完成,而標準輸入輸出的文件描述符則需要通過 FileDescriptor 的靜態字段獲取。

字符流

通過字節流可以完成很多 I/O 操作,但是如果連文本文件的處理都通過字節流來完成的話就太麻煩了。因此,為了解決這樣的問題,
字符流便誕生了。

字符流 是對 字節流 的一層封裝,在 Java 中通過 ReaderWriter 這兩個抽象類來表示。

和 InputStream 和 OutputStream 一樣,具體的功能將由它們的子類實現,而它們則提供一些基本的接口,這裏列舉出最基本的接口:

  • Reader:

    方法 作用
    int read() 讀取單個字符,返回值是該字符的碼點,到達流的末尾就返回 -1
  • Writer:

    方法 作用
    void write(int c) 寫入單個字符
    void write(String str) 寫入字符串
    abstract void flush() 將緩存的數據全部寫入目標

需要註意的是,這裏的 read() 方法和 write() 都不是抽象方法了,因為這兩個方法實際上都是調用內部的 字節流 完成工作,因此,只需要相應的字節流實現基本的功能就足夠了。

常用實現

Java 字符流實現中最常用的應該是 InputStreamReaderOutputStreamWriter 了,它們的構造方法如下:

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 提供了這兩個類的子類 FileReaderFileWriter, 使用這兩個類可以省略手動創建字節流的過程,具體內容可以查看相關文檔。

緩沖區

I/O 操作的一個常識:頻繁的 I/O 操作的效率是很低的,所以我們加一個緩沖區吧!

Java 中我們可以通過 BufferedInputStreamBufferedOutputStream 為字節流添加緩沖區,通過 BufferedReaderBufferedWriter 為字符流添加緩沖區。

這樣一來,一段經典的代碼就成型了:

// 字節流 -> 字符流 -> 緩沖區
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("example.txt")));

很完美是不是!

各種字節流

雖然說最常用的字節流式文件字節流(大概),但是,輸入輸出的環境是復雜的,處理文件以外,其他的設備如內存、網絡等都可能用到輸入輸出,
而且不同的條件下需要的功能還不一樣。

字符流的需求相對來說較為統一,因此一般情況下 InputStreamReaderOutputStreamWriter 完全可以一統天下,但是對於 字節流 來說,Java 提供了各種各樣的實現。

尤其是 FilterInputStreamFilterOutputStream 的子類,它們都有以輸出/輸出流作為參數的構造方法,因此,我們可以將不同的 Filter 組裝起來,得到我們想要的功能。

這是除了 字節-字符-緩沖 以外讓我覺得最 Beautiful 的設計,能夠靈活的適應各種需求。

這些 Filter 的使用可以查看官方文檔或者看看《Java 核心技術卷卷二》的 2.1.3 節,這是相當棒的功能。

結語

寫完博客回頭看,感覺質量有點差……

篇幅太少了,很多東西都沒有說清楚,屬於適合自己回顧的博客 @[email protected]

另外,很想吐槽的是:作為面向對象的語言,Java 內置的網絡庫居然沒有將 請求響應 這兩個對象分開!!!

用起來各種不順手……

Java 中的 I/O 抽象