1. 程式人生 > >系統學習 Java IO (十三)----字元讀寫 Reader/Writer 及其常用子類

系統學習 Java IO (十三)----字元讀寫 Reader/Writer 及其常用子類

Reader

Reader 類是 Java IO API 中所有 Reader 子類的基類。 Reader 類似於 InputStream ,除了它是基於字元而不是基於位元組的。 換句話說,Java Reader 用於讀取文字,而 InputStream 用於讀取原始位元組。

Writer

Writer 類是 Java IO API 中所有 Writer 子類的基類。 Writer 就像一個 OutputStream ,除了它是基於字元而不是基於位元組的。 換句話說,Writer 用於寫入文字,而 OutputStream 用於寫入原始位元組。
Writer 通常連線到某些資料目標,如檔案,字元陣列,網路套接字等。

Unicode中的字元

許多應用程式使用 UTF(UTF-8或UTF-16)來儲存文字資料。 可能需要一個或多個位元組來表示 UTF-8 中的單個字元。 在 UTF-16 中,每個字元需要 2 個位元組來表示。 因此,在讀取或寫入文字資料時,資料中的單個位元組可能與 UTF 中的一個字元不對應。 如果只是通過 InputStream 一次讀取或寫入 UTF-8 資料的一個位元組,並嘗試將每個位元組轉換為字元,可能不會得到正確的文字。

Reader 類能夠將位元組解碼為字元。 只需要告訴Reader要解碼的字符集。 這是在例項化 Reader 時執行的(當例項化其中一個子類時)。 通常會直接使用 Reader 子類而不是 Reader。Writer 同理。

讀取

部分方法如下:
|方法|描述|
|-|-|
| void mark(int readAheadLimit) | 標記流中的當前位置。
| int read() | 讀取單個字元。
| int read(char[] cbuf) |將字元讀入陣列。
|abstract int read(char[] cbuf, int off, int len) | 將字元讀入陣列的某一部分。
| int read(CharBuffer target) | 試圖將字元讀入指定的字元緩衝區。
|boolean ready() | 判斷是否準備讀取此流。

具體的使用需要參考對應的子類。

和 InputStream 類似,如果 read() 方法返回 -1 ,則 Reader 中沒有更多資料要讀取,並且可以關閉它。-1 作為 int 值,而不是-1作為byte或char值。

將位元組流包裝成字元流 InputStreamReader/OutputStreamWrite

InputStreamReade

InputStreamReade 類用於包裝 InputStream ,從而將基於位元組的輸入流轉換為基於字元的 Reader 。 換句話說,InputStreamReader 將 InputStream 的位元組解釋為文字而不是數字資料,是位元組流通向字元流的橋樑。
為了達到最高效率,可要考慮在 BufferedReader 內包裝 InputStreamReader。例如:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

通常用於從檔案(或網路連線)中讀取字元,其中位元組表示文字。 例如,一個文字檔案,其中字元編碼為 UTF-8 。 可以使用InputStreamReader 來包裝 FileInputStream 以讀取此類檔案。
一個示例如下:

 InputStream inputStream = new FileInputStream("D:\\test\\1.txt");
        Reader inputStreamReader = new InputStreamReader(inputStream);

        int data = inputStreamReader.read();
        while (data != -1) {
            char c = (char) data;
            System.out.print(c);
            data = inputStreamReader.read();
        }
        inputStreamReader.close();

首先建立一個 FileInputStream ,然後將其包裝在 InputStreamReader 中。 其次,該示例通過InputStreamReader 從檔案中讀取所有字元.
注意:為清楚起見,此處已跳過正確的異常處理。 要了解有關正確異常處理的更多資訊,請轉至目錄的 Java IO 異常處理。

指定字符集

底層 InputStream 中的字元將使用某些字元編碼進行編碼。 此字元編碼稱為字符集,Charset。 兩種常用字符集是 ASCII 和 UTF8(在某些情況下為UTF-16)。

預設字符集可能因為環境不同而不同,所以建議告訴 InputStreamReader 例項 InputStream 中的字元用什麼字符集進行編碼。 這可以在 InputStreamReader 建構函式中指定,可以只提供字符集的名字字串,在底層會呼叫Charset.forName("UTF-8")進行轉換的。 以下是設定Java InputStreamReader使用的字符集的示例:

InputStream inputStream = new FileInputStream("D:\\test\\1.txt");
Reader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
關閉 InputStreamReader

同樣建議使用 Java try with resources 來關閉流。

try(InputStreamReader inputStreamReader =
    new InputStreamReader(input)){
    int data = inputStreamReader.read();
    while(data != -) {
        System.out.print((char) data));
        data = inputStreamReader.read();
    } 
}

OutputStreamWriter

OutputStreamWriter 類用於包裝 OutputStream ,從而將基於位元組的輸出流轉換為基於字元的 Writer 。

如果需要將字元寫入檔案,OutputStreamWriter 非常有用,例如編碼為 UTF-8或UTF-16。 然後可以將字元(char值)寫入 OutputStreamWriter ,它將正確編碼它們並將編碼的位元組寫入底層的 OutputStream 。
一個簡單的示例如下:

OutputStream outputStream = new FileOutputStream("D:\\test\\1.txt");
Writer outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.write("Hello OutputStreamWriter");
outputStreamWriter.close();

注意:為清楚起見,此處已跳過正確的異常處理。 要了解有關正確異常處理的更多資訊,請轉至目錄的 Java IO 異常處理。

同 InputStreamReader,OutputStreamWriter 也可以使用指定的字符集輸出,如:
Writer outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");

關閉 OutputStreamReader

參考關閉 InputStreamReader 或 Java IO 異常處理。

讀寫檔案 FileReader/FileWriter

FileReader

FileReader類使得可以將檔案的內容作為字元流讀取。 它的工作方式與 FileInputStream 非常相似,只是 FileInputStream 讀取位元組,而 FileReader 讀取字元。 換句話說,FileReader 旨在讀取文字。 取決於字元編碼方案,一個字元可以對應於一個或多個位元組。

FileReader 假定您要使用執行應用程式的計算機的預設字元編碼來解碼檔案中的位元組。 這可能並不總是你想要的,但是不能改變它!
如果要指定其他字元解碼方案,請不要使用 FileReader 。 而是在 FileInputStream 上使用 InputStreamReader 。 InputStreamReader 允許您指定在讀取基礎檔案中的位元組時使用的字元編碼方案。

FileWriter

FileWriter 類可以將字元寫入檔案。 在這方面它的工作原理與 FileOutputStream 非常相似,只是 FileOutputStream 是基於位元組的,而 FileWriter 是基於字元的。 換句話說,FileWriter 用於寫文字。 一個字元可以對應於一個或多個位元組,這取決於使用的字元編碼方案。
建立 FileWriter 時,可以決定是否要覆蓋具有相同名稱的任何現有檔案,或者只是要追加內容到現有檔案。 可以通過選擇使用的 FileWriter 建構函式來決定。

FileWriter(File file, boolean append) :根據給定的 File 物件構造一個 FileWriter 物件。 示例如下:

Writer fileWriter = new FileWriter("c:\\data\\output.txt", true);  // 追加模式
Writer fileWriter = new FileWriter("c:\\data\\output.txt", false); // 預設情況,直接覆蓋原檔案

注意,只要成功 new 了一個 FileWriter 物件,沒有指定是追加模式的話,那不管有沒有呼叫 write() 方法,都會清空檔案內容。

下面是一個讀和寫的例子:

public class FileRW {
    public static void main(String[] args) throws IOException {
        // 預設是覆蓋模式
        File file = new File("D:\\test\\1.txt");
        Writer writer1 = new FileWriter(file);
        writer1.write("string from writer1, ");
        writer1.close();

        Writer writer2 = new FileWriter(file, true);
        writer2.write("append content from writer2");
        writer2.close();


        Reader reader = new FileReader(file);
        int data = reader.read();
        while (data != -1) {
            // 將會輸出 string from writer1, append content from writer2
            System.out.print((char) data);
            data = reader.read();
        }
        reader.close();
    }
}

注意:為清楚起見,此處已跳過正確的異常處理。 要了解有關正確異常處理的更多資訊,請轉至Java IO異常處理。

其他行為和 InputStreamReader 差不多,就不展開講了。

PipedReader/PipedWriter

讀寫管道 PipedReader 和 PipedWriter

PipedReader 類使得可以將管道的內容作為字元流讀取。 因此它的工作方式與 PipedInputStream 非常相似,只是PipedInputStream 是基於位元組的,而不是基於字元的。 換句話說,PipedReader 旨在讀取文字。PipedWriter 同理。

構造器
方法 描述
PipedReader() 建立尚未連線的 PipedReader。
PipedReader(int pipeSize) 建立一個尚未連線的 PipedReader,並對管道緩衝區使用指定的管道大小。
PipedReader(PipedWriter src) 建立直接連線到傳送 PipedWriter src 的 PipedReader。
PipedReader(PipedWriter src, int pipeSize) 建立一個 PipedReader,使其連線到管道 writer src,並對管道緩衝區使用指定的管道大小。
PipedWriter() 建立一個尚未連線到傳送 reader 的傳送 writer。
PipedWriter(PipedReader snk) 建立傳送 writer,使其連線到指定的傳送 reader。
讀寫之前,必須先建立連線

PipedReader 必須連線到 PipedWriter 才可以讀 ,PipedWriter 也必須始終連線到 PipedReader 才可以寫。就是說讀寫之前,必須先建立連線,有兩種方式可以建立連線。

  1. 通過構造器建立,虛擬碼如 Piped piped1 = new Piped(piped2);
  2. 呼叫其中一個的 connect() 方法,虛擬碼如 Piped1.connect(Piped2);

並且通常,PipedReader 和 PipedWriter 由不同的執行緒使用。 注意只有一個 PipedReader 可以連線到同一個 PipedWriter 。
一個示例如下:

PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader(writer);

writer.write("string form pipedwriter");
writer.close();

int data = reader.read();
while (data != -1) {
        System.out.print((char) data); // string form pipedwriter
        data = reader.read();
    }
reader.close();

注意:為清楚起見,這裡忽略了正確的 IO 異常處理,並且沒有使用不同執行緒,不同執行緒操作請參考 PipedInputStream 。

正如在上面的示例中所看到的,PipedReader 需要連線到 PipedWriter 。 當這兩個字元流連線時,它們形成一個管道。 要了解有關 Java IO 管道的更多資訊,請參考 管道流 PipedInputStream 部分。

讀寫字元陣列 CharArrayReader/CharArrayWriter

ByteArrayInputStream/ByteArrayOutputStream 是對位元組陣列處理,CharArrayReader/CharArrayWriter 則是對字元陣列進行處理,其用法是基本一致的,所以這裡略微帶過。

CharArrayReader

CharArrayReader 類可以將 char 陣列的內容作為字元流讀取。
只需將 char 陣列包裝在 CharArrayReader 中就可以很方便的生成一個 Reader 物件。

CharArrayWriter

CharArrayWriter 類可以通過 Writer 方法(CharArrayWriter是Writer的子類)編寫字元,並將寫入的字元轉換為 char 陣列。
在寫入所有字元時,CharArrayWriter 上呼叫 toCharArray() 能很方便的生成一個字元陣列。

兩個類的建構函式:
方法 描述
CharArrayReader(char[] buf) 根據指定的 char 陣列建立一個 CharArrayReader
CharArrayReader(char[] buf, int offset, int length) 根據指定的 char 陣列建立一個 CharArrayReader
CharArrayWriter() 建立一個新的 CharArrayWriter ,預設緩衝區大小為 32
CharArrayWriter(int initialSize) 建立一個具有指定初始緩衝區大小的新 CharArrayWriter

注意:設定初始大小不會阻止 CharArrayWriter 儲存比初始大小更多的字元。 如果寫入的字元數超過了初始 char 陣列的大小,則會建立一個新的更大的 char 陣列,並將所有字元複製到新陣列中。

一個使用例項如下:

CharArrayWriter writer = new CharArrayWriter();
writer.append('H');
writer.write("ello ".toCharArray());
writer.write("World");
char[] chars = writer.toCharArray();
writer.close();

CharArrayReader reader = new CharArrayReader(chars);
int data = reader.read();
while (data != -1) {
    System.out.print((char) data); // Hello World
    data = reader.read();
}
reader.close();

注意:為清楚起見,此處已跳過正確的異常處理。 要了解有關正確異常處理的更多資訊,請轉至 Java IO 異常處理。