Java I/O類庫從加入到 Java 核心類庫之後,已經經過了三個階段
I/O | since | type | descripe |
---|---|---|---|
BIO | 1.4版本之前 | 同步阻塞 | 一個鏈接一個線程 |
NIO | 1.4版本之後 | 同步非阻塞 | 一個請求一個線程 |
AIO | 1.7版本之後 | 異步非阻塞 | 一個有效請求一個線程 |
傳統 I/O
File 類
File 既能代表一個特定文件的名稱,又能代表一個目錄下的一組文件的名稱。假設我們想查看一個目錄列表,可以用如下兩種方法來使用 File 對象
1、如果我們調用不帶參數的 list() 方法,便可以獲得此 File 對象包含的全部列表
2、如果我們想獲得一個受限的列表,我們需要用到“目錄過濾器” – FilenameFilter
FilenameFilter
這個接口中,最重要的是 accept() 方法。
創建一個類事項 FilenameFilter 接口,目的就是將 accept() 方法提供給 list() 使用,使 list 可以回調 accept() ,進而可以決定哪些文件包含在列表中,這是一個 策略模式 的例子。
// IO.File.DirTest1 代碼來自 Thinking in Java
public class DirTest {
// 採用匿名類的形式
public static FilenameFilter filter(final String regex) {
return new FilenameFilter(){
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name){
// 按照正則表達式過濾文件名
return pattern.matcher(name).matches();
}
};
}
public static void main(String[] args) {
File path = new File(".");
String[] list;
if (args.length == 0) {
list = path.list();
} else {
list = path.list(filter(args[0]));
}
//按照字母順序進行排序
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for (String dirItem : list) {
System.out.println(dirItem);
}
}
}
list() 方法實現了基本的功能,而且按照 FilenameFilter 的形式提供了這個策略,以便完善 list() 在提供服務時所需的算法。list() 會爲當前目錄對象下的每個文件名稱調用 accept() 方法,判斷結果由 accept() 返回的布爾值表示。
上例中的 FilenameFilter 採用匿名類的形式提供服務,內部實現 accept() 方法:按照正則表達式過濾文件名。
對於 File 對象,我們可以使用 File 對象來創建新的目錄或者尚不存在的整個目錄路徑。
輸入和輸出
在衆多編程語言中,都設計 流 這個抽象概念來設計 I/O 類庫,流 代表任何有能力產出數據的數據源對象或者是有能力接受數據的接收端對象。流 屏蔽了實際的 I/O 設備中處理數據的細節。
Java 類庫將其 I/O 庫分爲了兩部分:
A:通過繼承,任何自 InputStream 或者 Reader 派生來的類,這些類都有基本方法 read(),用於讀取單個字節或者字節數組。
B:通過繼承,任何自 OutputStream 或者 Writer 派生來的類,這些類都有基本方法 write(),用於寫單個字節或者字節數組。
InputStream 與 OutputStream
由Java 1.0 提供
InputStream 派生類
InputStream的作用是用來表示那些從不同數據源產生輸入的類。
數據源 | 類名 | 構造參數 | 描述 |
---|---|---|---|
字節數組 | ByteArrayInputStream | 緩衝區 | 允許將內存的緩衝區當做InputStream使用 |
String對象 | StringBufferInputStream | 字符串 | 將String轉換成InputStream,底層實現採用 StringBuffer |
文件 | FileInputStream | 字符串 | 用於從文件中讀取信息 |
管道 | PipedInputStream | PipedOutputStream[管道化概念] | 作爲多線程中數據源,產生用於寫入相關 PipedOutputStream的數據。 |
流序列 | SequenceInputStream | 兩個InputStream對象/容納多個流對象的容器Enumeration | 將兩個或多個InputStream對象轉換爲一個 |
其他數據源 | FilterInputStream | InputStream | FilterInputStream也屬於一種InputStream,爲“裝飾器”類提供基類 |
裝飾器類必須具有和它裝飾的對象相同的接口,儘管可以擴展,但是是個例。
OutputStream 派生類
該類決定輸出所要去往的目標:字節數組(但不是 String,不過你當然可以使用字節數組自己創建)、文件或者管道。
類名 | 構造參數 | 描述 |
---|---|---|
ByteArrayOutputStream | 可選的初始化大小參數 | 在內存中創建緩衝區,所有送往“流”的數據都要放置在此緩衝區中 |
FileOutputStream | 字符串 | 將數據寫入文件 |
PipedOutputStream | PipedOutputStream | 實現“管道化”概念,多線程中數據目的地 |
FilterOutputStream | OutputStream | 抽象類,作爲“裝飾器”的接口。 |
FilterInputStream與FilterOutputStream
Java I/O 類庫需要多種不同的功能組合,這是採用“裝飾器模式”的理由所在(裝飾器經常用於滿足各種可能的組合)。這同時也是Java I/O 類庫中存在 Filter(過濾器)的原因之一,抽象類 Filter 是所有裝飾器類的基類。
裝飾器類在我們編寫程序代碼時提供了相當多的靈活性(我們可以容易的匹配和混合屬性),但是它也同時增加了代碼的複雜性:我們有時需要創建許多類——核心 I/O 與其他裝飾器類,他們共同成爲了我們希望的單個 I/O 對象。
類 | 功能 | 如何使用 |
---|---|---|
DataInputStream | 按照可移植方式向流中讀取基本類型數據 | 包含用於讀取基本類型數據的接口 |
DataOutputStream | 按照可移植方式向流中寫入基本類型數據 | 包含用於寫入基本類型數據的接口 |
BufferedInputStream | “使用緩衝區”,防止每次操作讀取都進行實際的讀操作 | 本質上不提供接口,只不過是向進程中添加緩衝區所必須的屬性 |
BufferedOutputStream | “使用緩衝區”,防止每次操作讀取都進行實際的寫操作 | 本質上不提供接口,只不過是向進程中添加緩衝區所必須的屬性 |
LineNumberInputStream | 可跟蹤行號的輸入流 | 傳入一個InputStream。僅增加了一個行號,因此可能要與接口對象搭配使用 |
PushbackInputStream | 具有“能彈出一個字節的緩衝區”。可將讀到的最後一個字符回退 | 通常作爲編譯器的掃描器,我們可能永遠不會用到 |
PrintStream | 用於產生格式化輸出。DataOutputStream處理數據的存儲,PrintStream處理顯示 | 傳入一個OutputStream,可以用boolean指示是否該在每次換行時清空緩衝區(可選) |
Reader 與 Writer
Java 1.1對基本的I/O類庫進行了重大的修改,在 InputStream 和 OutputStream 的基礎上,增加了 Reader 與 Writer。InputStream 和 OutputStream 在以面向字節形式的I/O中仍可以提供極有價值的服務,Reader和Writer則提供兼容Unicode與面向字符的I/O功能。另外:
1.Java 1.1 向InputStream 和 OutputStream 繼承層次結構中添加了一些新類
2.有時我們需要把來自於“字節”與“字符”層次結構的類結合使用,互相轉化。爲了實現這個目的,Java 類庫爲程序員們提供了“適配器(adapter)”類:InputStreamReader 可以把 InputStream 轉換爲 Reader,而OutputStreamWriter 可以把 OutputStream 轉換爲 Writer。
對於大部分 I/O 操作,都有可用的 Reader 與 Writer 類,但是有些面向“字節”的情況下,InputStream與OutputStream依然是正確的解決方案,比如 java.util.zip 類庫就是面向“字節”的。
下面我們用兩個表格來熟悉 Java I/O流 與 I/O裝飾器 ;兩個方面的 Java 1.0 與 Java 1.1 的對應類與接口
Java I/O 流
大體上,我們會發現,這兩個不同的繼承層次結構中的接口即使不能完全相同,但也非常相似。
Java 1.0 | Java 1.1 |
---|---|
InuptStream | Reader |
OutputStream | Writer |
FileInputStream | FileReader |
FileOutputStream | FileWriter |
StringBufferInputStream(棄用) | StringReader |
– | StringWriter |
ByteArrayInputStream | CharArrayReader |
ByteArrayOutputStream | CharArrayWriter |
PipedInputStream | PipedReader |
PipedOutputStream | PipedWriter |
I/O 裝飾器
對於InputStream和OutputStream來說,我們會使用FilterInputStream和OutputStream的裝飾器子類來修改“流”以滿足特殊需要。Reader和Writer的類繼承層次結構繼續沿用相同的思想——儘管不完全相同
Java 1.0 | Java 1.1 |
---|---|
FilterInputStream | FilterReader |
FilterOutputStream | FilterWriter(抽象類) |
BufferedInputStream | BufferedReader(readLine()) |
BufferedOutputStream | BufferedWriter |
DataInputStream | DataInputStream/BufferedReader |
PrintStream | PrintWriter |
LineNumberInputStream(棄用) | LineNumberReader |
StreamTokenizer | StreamTokenizer(使用接受Reader的構造器) |
PushbackInputStream | PushbackReader |
標準 I/O
程序所有的輸入都可以來自於標準輸入 System.in,所有的輸出都可以發送到標準輸出System.out,所有的錯誤信息也都可以發送到標準錯誤 System.err。
其中 System.out 與 System.err 都已經被包裝爲 PrintStream 對象,但是 System.in 卻是一個沒有被包裝過的未經加工的 InputStream。這表示我們可以直接使用 System.out 與 System.err,但是在使用 System.in 進行數據輸入之前必須對其進行包裝。
比如:Scanner scan = new Scanner(System.in);
將 System.out 轉換成 PrintWriter
System.out 是一個 PrintStream,PrintStream 是一個 OutputStream,而 PrintWriter 還有個接受 OutputStream 做參數的構造器。所以如有需要,我們可以將 System.out 轉換成 PrintWriter
public static void main(String[] args) {
// 將PrintWriter構造器的第二個參數設計爲true,開啓自動清空
PrintWriter out = new PrintWriter(System.out, true);
out.println("Hello world!");
}
標準 I/O 重定向
Java 的 System 類提供了一些簡單的靜態方法調用,以允許我們對標準輸入、輸出和錯誤 I/O 流進行重定向:
setIn(InputStream)
setOut(PrintStream)
setErr(PrintStream)
I/O 重定向操作的是字節流,而不是字符流
後記
暫告一段落,後面分別爲 NIO 與 AIO
掃一下吧,隨便掃一下~