跳轉到內容

75% developed
來自華夏公益教科書,開放的書籍,開放的世界

導航 使用者介面 主題: v  d  e )


Java 中最基本輸入和輸出 (System.inSystem.out 欄位,已在 基本 I/O 中使用) 是透過流完成的。流是表示資料來源和資料目標的物件。作為資料來源的流可以從中讀取,而作為資料目標的流可以向其中寫入。Java 中的流是長度不確定的位元組的有序序列。流是有序且按順序排列的,以便 Java 虛擬機器能夠理解並處理該流。流類似於水流。它們像通訊中的電磁波一樣,作為通訊媒介存在。Java 流中的位元組順序或序列使虛擬機器能夠將其歸類於其他流。

Java 具有各種內建流,這些流在 java.io 包中作為類實現,例如 System.inSystem.out 的類。流可以被歸類為輸入流和輸出流。所有 Java 流都源自輸入流 (java.io.InputStream) 和輸出流 (java.io.OutputStream) 類。它們是為其他流類準備的抽象基類。System.in 是輸入流類衍生物,而 System.out 則是輸出流類衍生物。兩者都是用於直接與控制檯進行互動的基本類,類似地,System.err 也遵循此規則。Java 還擁有用於在程式的不同部分甚至執行緒之間進行通訊的流。還有一些類可以“過濾”流,將一種格式更改為另一種格式 (例如,類 DataOutputStream,它將各種原始型別轉換為位元組流)。

流的一個特點是它們一次只處理一個離散的資料單元,並且不同的流處理不同型別的資料。例如,如果有一個表示位元組目標的流,那麼可以一次傳送一個位元組的資料到該目標。如果一個流是位元組資料的源,那麼可以一次讀取一個位元組的資料。由於這是訪問流中資料的唯一方式,因此在後一種情況下,在實際到達流的末尾之前,我們並不知道何時已從流中讀取了所有資料。在讀取流時,通常必須在每次讀取操作中檢查每個資料單元,以檢視是否已到達流的末尾 (對於位元組流,特殊值為整數 -1 或 FFFF 十六進位制)。

輸入流

[編輯 | 編輯原始碼]

輸入流為我們編寫的 Java 應用程式/程式獲取位元組(例如檔案、陣列、鍵盤或顯示器等)。InputStream 是一個抽象類,它代表位元組資料的源。它有一個 read() 方法,該方法返回流中的下一個位元組,還有一個 close() 方法,該方法應在程式完成對流的操作時由程式呼叫。read() 方法是過載的,可以接受一個位元組陣列來讀取。它有一個 skip() 方法可以跳過一定數量的位元組,還有一個 available() 方法,程式可以使用它來確定立即可讀的位元組數,因為並非所有資料都一定立即準備好。作為一個抽象類,它不能被例項化,但描述了輸入流的一般行為。一些具體子類的例子是 ByteArrayInputStream,它從位元組陣列讀取,和 FileInputStream,它從檔案讀取位元組資料。

以下示例 中,我們在螢幕上多次列印 "Hello world!"。列印訊息的次數儲存在一個名為 source.txt 的檔案中。此檔案應只包含一個整數,並應放在與 ConfiguredApplication 類相同的資料夾中。

Computer code 程式碼清單 9.1:輸入流示例。
import java.io.File;
import java.io.FileInputStream;
 
public class ConfiguredApplication {
 
  public static void main(String[] args) throws Exception {
 
    // Data reading
    File file = new File("source.txt");
    FileInputStream stream = new FileInputStream(file);
 
    StringBuffer buffer = new StringBuffer();
 
    int character = 0;
    while ((character = stream.read()) != -1) {
      buffer.append((char) character);
    }
 
    stream.close();
 
    // Data use
    Integer readInteger = Integer.parseInt(buffer.toString());
    for (int i = 0; i < readInteger ; i++) {
      System.out.println("Hello world!");
    }
  }
}

close() 方法並不總是強制性的,但可以避免一些程序間併發衝突。但是,如果它在 read()write()(在同一個程序中)之前發生,它們將返回警告 Stream closed

該類開始使用 File 物件來識別檔名。File 物件由輸入流用作流的源。我們建立了一個緩衝區和一個字元來準備資料載入。緩衝區將包含所有檔案內容,字元將臨時包含檔案中存在的每個字元,一次一個。這是在 while{} 迴圈中完成的。迴圈的每次迭代都將從流複製一個字元到緩衝區。當流中不再存在字元時,迴圈結束。然後我們關閉流。程式碼的最後部分使用我們從檔案中載入的資料。它被轉換為字串,然後轉換為整數(因此資料必須是整數)。如果它有效,該整數將用於確定我們在螢幕上列印 "Hello world!" 的次數。為了可讀性,沒有定義 try/catch 塊,但應捕獲丟擲的異常。

讓我們嘗試使用以下原始檔

Computer code 程式碼清單 9.2:source.txt

4

我們應該得到這個

Standard input or output ConfiguredApplication 的輸出
$ java ConfiguredApplication
Hello world!
Hello world!
Hello world!
Hello world!
Warning 如果顯示 FileNotFoundExceptionIOException,則源可能沒有放在正確的資料夾中或其名稱拼寫錯誤。
如果顯示 NumberFormatException,則檔案的內容可能不是整數。

還有 Reader,它是一個抽象類,代表字元資料的源。它類似於 InputStream,只是它處理的是字元而不是位元組(請記住,Java 使用 Unicode,因此一個字元是 2 個位元組,而不是一個)。它的方法通常與 InputStream 的方法相似。具體子類包括 FileReader 等類,它從檔案讀取字元,以及 StringReader,它從字串讀取字元。你還可以使用 InputStreamReader 類將 InputStream 物件轉換為 Reader 物件,該類可以“包裝”在 InputStream 物件周圍(透過將其作為引數傳遞給它的建構函式)。它使用字元編碼方案(程式設計師可以更改)將位元組轉換為 16 位 Unicode 字元。

輸出流

[edit | edit source]

輸出流將位元組流從我們的程式或應用程式定向到外部環境。OutputStream 是一個抽象類,它是 InputStream 的目標對應類。OutputStream 有一個 write() 方法,可用於將位元組寫入流。該方法是過載的,可以接受一個數組。close() 方法在應用程式完成對流的操作時關閉流,它還有一個 flush() 方法。流可能會等到有了一定數量的位元組才將其一次性寫入,以提高效率。如果流物件在寫入之前緩衝了任何資料,則 flush() 方法將強制它寫入所有這些資料。與 InputStream 一樣,該類不能被例項化,但它有與 InputStream 相對應的具體子類,例如 ByteArrayOutputStreamFileOutputStream 等。

以下示例 中,我們將當前時間儲存在一個名為 log.txt 的已經存在的檔案中,該檔案位於與該類相同的資料夾中。

Computer code 程式碼清單 9.2:輸出流示例。
import java.io.File;
import java.io.FileOutputStream;
import java.util.Date;
 
public class LogTime {
    public static void main(String[] args) throws Exception {
        // Generate data
        String timeInString = new Date().toString();

        // Store data
        File file = new File("log.txt");
        FileOutputStream stream = new FileOutputStream(file);

        byte[] timeInBytes = timeInString.getBytes();

        stream.write(timeInBytes);
        stream.flush();
        stream.close();
    }
}

這種情況比較簡單,因為我們可以同時將所有資料放入流中。程式碼的第一部分生成一個包含當前時間的字串。然後我們建立一個 File 物件,它識別輸出檔案,併為該檔案建立一個輸出流。我們將資料寫入流,重新整理它並關閉它。就這樣。為了可讀性,沒有定義 try/catch 塊,但應捕獲丟擲的異常。

Warning 為了從開頭多次讀取文字檔案,應引入一個 FileChannel 變數,僅用於重新定位讀取器。

現在讓我們執行它

Standard input or output LogTime 執行
$ java LogTime

我們應該得到這個內容

Computer code 程式碼清單 9.4:log.txt

Tue Oct 8 10:50:44 CEUTC 2024

Warning 如果顯示 FileNotFoundExceptionIOException,則該檔案可能沒有建立或沒有放在正確的資料夾中。

還有 Writer,它是 OutputStream 的字元對應類,也是 Reader 的目標對應類,它也是一個抽象超類。特定的實現與 Reader 的實現相平行,例如 FileWriterStringWriterOutputStreamWriter,用於將常規 OutputStream 轉換為讀取器,以便它可以接受字元資料。

System.outSystem.err

[edit | edit source]

Systemjava.lang 包中的一個類,它有一些可供 Java 程式使用的靜態成員。兩個對控制檯輸出有用的成員是 System.outSystem.err。System.out 和 System.err 都是 PrintStream 物件。PrintStreamFilterOutputStream 的子類,FilterOutputStream 本身是 OutputStream(上面討論過)的子類,它的主要目的是將各種資料型別轉換為位元組流,這些位元組流根據某種編碼方案用字元表示該資料。

System.outSystem.err 都將文字顯示到控制檯中,使用者可以在其中讀取它,但是這的確切含義取決於使用的平臺和執行程式的環境。例如,在 BlueJay 和 Eclipse IDE 中,有一個特殊的“終端”視窗將顯示此輸出。如果程式在 Windows 中啟動,則輸出將傳送到 DOS 提示符(通常這意味著你必須從命令列啟動程式才能看到輸出)。

System.outSystem.err 的區別在於它們應該用於什麼。System.out 應該用於正常的程式輸出,System.err 應該用於通知使用者程式中發生了某種錯誤。在某些情況下,這可能很重要。例如,在 DOS 中,使用者可以將標準輸出重定向到其他目的地(例如檔案),但錯誤輸出不會被重定向,而是顯示在螢幕上。如果沒有這種情況,使用者可能永遠無法知道發生了錯誤。


Clipboard

待辦事項
說明 print() 方法是如何工作的,強調該方法不會拆分行,但可以透過 \n 轉義序列來實現。談談它如何處理不同的資料型別,然後介紹 println() 方法作為一種方便的方法,它會自動新增 \n 字元。


新的 I/O

[edit | edit source]

J2SE 1.4 之前的 Java 版本只支援基於流的阻塞 I/O。這需要每個流處理一個執行緒,因為在活動的執行緒阻塞等待輸入或輸出時,無法進行其他處理。對於任何需要實現 Java 網路服務的人來說,這是一個主要的擴充套件性和效能問題。自 J2SE 1.4 引入 NIO (New I/O) 以來,透過引入非阻塞 I/O 框架解決了擴充套件性問題(儘管在 Oracle 實現的 NIO API 中存在許多開放問題)。

非阻塞 IO 框架雖然比最初的阻塞 IO 框架複雜得多,但允許單個執行緒處理任意數量的“通道”。該框架基於 Reactor 模式。

更多資訊

[edit | edit source]

有關 java.io 包內容的更多資訊,請單擊此連結在 Oracle 網站上檢視 (http://docs.oracle.com/javase/7/docs/api/index.html).


Clipboard

待辦事項
新增一些類似於 變數 中的練習


華夏公益教科書