1. 程式人生 > 實用技巧 >JAVA 流總結

JAVA 流總結

目錄

1. IO流

1.1 IO流概述

生活中,你肯定經歷過這樣的場景。當你編輯一個文字檔案,忘記了 ctrl+s ,可能檔案就白白編輯了。當你電腦 上插入一個U盤,可以把一個視訊,拷貝到你的電腦硬盤裡。那麼資料都是在哪些裝置上的呢?鍵盤、記憶體、硬 盤、外接裝置等等。

我們把這種資料的傳輸,可以看做是一種資料的流動,按照流動的方向,以記憶體為基準,分為 輸入input

和 輸出 output ,即流向記憶體是輸入流,流出記憶體的輸出流。 Java中I/O操作主要是指使用 java.io 包下的內容,進行輸入、輸出操作。輸入也叫做從記憶體讀取資料,輸出也叫做作從記憶體寫出資料。

1.2 IO流的分類

根據資料的流向分為:輸入流和輸出流。

  • 輸入流 :把資料從其他裝置上讀取到記憶體中的流。
  • 輸出流 :把資料從記憶體中寫出到其他裝置上的流。

根據資料的型別分為:位元組流和字元流。

  • 位元組流 :以位元組為單位,讀寫資料的流。
  • 字元流 :以字元為單位,讀寫資料的流

1.3 位元組流

一切檔案資料(文字、圖片、視訊等)在儲存時,都是以二進位制數字的形式儲存,都是一個一個的位元組,那麼傳輸時一 樣如此。所以,位元組流可以傳輸任意檔案資料。在操作流的時候,我們要時刻明確,無論使用什麼樣的流物件,底層傳輸的始終為二進位制資料。

1.3.1 位元組輸出流

1.3.1.1 超類OutputStream

java.io.OutputStream 抽象類是表示位元組輸出流的所有類的超類,將指定的位元組資訊寫出到目的地。它定義了位元組輸出流的基本共性功能方法。

  • public void close() :關閉此輸出流並釋放與此流相關聯的任何系統資源。
  • public void flush() :重新整理此輸出流並強制任何緩衝的輸出位元組被寫出。
  • public void write(byte[] b) :將 b.length位元組從指定的位元組陣列寫入此輸出流。
  • public void write(byte[] b, int off, int len) :從指定的位元組陣列寫入 len位元組,從偏移量 off開始輸 出到此輸出流。
  • public abstract void write(int b) :將指定的位元組寫入此輸出流。

注意:當完成流的操作時,必須呼叫close方法,釋放系統資源。

1.3.1.2 常用子類FileOutputStream

java.io.FileOutputStream 類是檔案輸出流,用於將資料寫出到檔案。

(1)構造方法

  • public FileOutputStream(File file) :建立一個向指定 File 物件表示的檔案中寫入資料的檔案輸出流。
  • public FileOutputStream(String name,boolean append) :建立一個向具有指定 name 的檔案中寫入資料的輸出檔案流。如果第二個引數為 true,則將位元組寫入檔案末尾處,而不是寫入檔案開始處。
  • public FileOutputStream(String name) : 建立一個向具有指定名稱的檔案中寫入資料的輸出檔案流。

當你建立一個流物件時,必須傳入一個檔案路徑。該路徑下,如果沒有這個檔案,會建立該檔案。如果有這個文 件,會清空這個檔案的資料。

程式碼舉例:

public static void main(String[] args) {
    // 使用File物件建立流物件,如果第二個引數為 true,則將位元組寫入檔案末尾處,而不是寫入檔案開始處。
    File file = new File("a.txt",true);
    FileOutputStream fos = new FileOutputStream(file);
    // 使用檔名稱建立流物件,如果第二個引數為 true,則將位元組寫入檔案末尾處,而不是寫入檔案開始處。
    FileOutputStream fos = new FileOutputStream("b.txt",true);
}

(2)輸出位元組資料到檔案

  • write(int b) 方法,每次可以寫出一個位元組資料到檔案輸出流。

程式碼示例:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileOutputStream fos = new FileOutputStream("fos.txt");
    // 寫出資料
    fos.write(97); // 寫出第1個位元組
    fos.write(98); // 寫出第2個位元組
    fos.write(99); // 寫出第3個位元組
    // 關閉資源
    fos.close();
}
//檢視fos檔案,裡面插入了abc字元

注意:

  1. 雖然引數為int型別四個位元組,但是隻會保留一個位元組的資訊寫出。
  2. 流操作完畢後,必須呼叫close方法釋放系統資源。

(3)輸出位元組陣列到檔案

  • write(byte[] b) ,每次可以輸出一個位元組陣列的資料到檔案輸出流。

程式碼演示:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileOutputStream fos = new FileOutputStream("fos.txt");
    // 字串轉換為位元組陣列
    byte[] b = "滴答滴答".getBytes();
    // 寫出位元組陣列資料
    fos.write(b);
    // 關閉資源
    fos.close();
}
//檢視fos檔案內容為:滴答滴答

注意:使用陣列讀取,每次讀取多個位元組,減少了系統間的IO操作次數,從而提高了讀寫的效率,建議開發中使 用。

(4)輸出指定長度的位元組陣列到檔案

  • write(byte[] b, int off, int len) ,將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此檔案輸出流。

程式碼演示:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileOutputStream fos = new FileOutputStream("fos.txt");
    // 字串轉換為位元組陣列
    byte[] b = "abcde".getBytes();
    // 寫出從索引2開始,2個位元組。索引2是c,兩個位元組,也就是cd。
    fos.write(b,2,2);
    // 關閉資源
    fos.close();
}
//檢視fos檔案內容為:cd

(5)輸出指定長度的位元組陣列換行追加到檔案

FileOutPutStream的兩個構造方法FileOutputStream(String name,boolean append)、FileOutputStream(String name,boolean append)是用來建立往檔案追加內容的輸出流物件的,引數中都需要傳入一個boolean型別的值, true 表示追加資料, false 表示清空原有資料。

程式碼演示:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileOutputStream fos = new FileOutputStream("fos.txt",true);
    // 定義位元組陣列
    byte[] words = {97,98,99,100,101};
    // 遍歷陣列
    for (int i = 0; i < words.length; i++) {
        // 寫出一個位元組
        fos.write(words[i]);
        // 寫出一個換行, 換行符號轉成陣列寫出
        fos.write("\r\n".getBytes());
    }
    // 關閉資源
    fos.close();
}

注意:

  • Windows系統裡,每行結尾是回車+換行 ,即 \r\n ;
  • Unix系統裡,每行結尾只有換行 ,即 \n ;
  • Mac系統裡,每行結尾是 回車 ,即 \r 。從 Mac OS X開始與Linux統一。

1.3.2 位元組輸入流

1.3.2.1 超類 InputStream

java.io.InputStream 抽象類是表示位元組輸入流的所有類的超類,可以讀取位元組資訊到記憶體中。它定義了位元組輸入流的基本共性功能方法。

  • public void close() :關閉此輸入流並釋放與此流相關聯的任何系統資源。
  • public abstract int read() : 從輸入流讀取資料的下一個位元組。
  • public int read(byte[] b) : 從此輸入流中將最多 b.length 個位元組的資料讀入一個 byte 陣列中。
  • public read(byte[] b, int off, int len) : 從此輸入流中將最多 len 個位元組的資料讀入一個 byte 陣列中。

1.3.2.2 常用子類 FileInputStream

java.io.FileInputStream 類是檔案輸入流,從檔案中讀取位元組資料。

(1)構造方法

  • FileInputStream(File file) : 通過開啟與實際檔案的連線來建立一個 FileInputStream ,該檔案由檔案系 統中的 File物件 file命名。
  • FileInputStream(String name) : 通過開啟與實際檔案的連線來建立一個 FileInputStream ,該檔案由檔案 系統中的路徑名 name命名。

程式碼示例:

public static void main(String[] args) {
    // 使用File物件建立流物件
    File file = new File("a.txt");
    FileInputStream fos = new FileInputStream(file);
    // 使用檔名稱建立流物件
    FileInputStream fos = new FileInputStream("b.txt");
}

(2)從檔案讀取位元組資料

  • read 方法,每次可以讀取一個位元組的資料,提升為int型別,如果讀取到檔案末尾,則返回 -1

程式碼演示:

public static void main(String[] args) throws IOException{
    // 使用檔名稱建立流物件
    FileInputStream fis = new FileInputStream("read.txt");
    // 定義變數,儲存資料
    int b ;
    // 迴圈讀取
    while ((b = fis.read())!=‐1) {
        System.out.println((char)b);
    }
    // 關閉資源
    fis.close();
}

(3)從檔案讀取位元組陣列資料

  • read(byte[] b) ,從此輸入流中將最多 b.length 個位元組的資料讀入一個 byte 陣列中,讀取到末尾時,返回 -1 。

程式碼示例:

public static void main(String[] args) throws IOException{
    // 使用檔名稱建立流物件.
    FileInputStream fis = new FileInputStream("read.txt"); // 檔案中為abcde
    // 定義變數,作為有效個數
    int len ;
    // 定義位元組陣列,作為裝位元組資料的容器
    byte[] b = new byte[2];
    // 迴圈讀取
    while (( len= fis.read(b))!=‐1) {
        // 每次讀取後,把陣列的有效位元組部分,變成字串列印
        System.out.println(new String(b,0,len));// len 每次讀取的有效位元組個數
    }
    // 關閉資源
    fis.close();
}

注意:使用陣列讀取,每次讀取多個位元組,減少了系統間的IO操作次數,從而提高了讀寫的效率,建議開發中使 用。

1.3.3 位元組輸入流與輸出流結合

我們在進行檔案的複製時,就需要位元組輸入流與輸出流結合使用。

程式碼示例:

public static void main(String[] args) throws IOException {
    // 1.建立流物件
    // 1.1 指定要複製的資料來源
    FileInputStream fis = new FileInputStream("D:\\test.jpg");
    // 1.2 指定要複製的目的地
    FileOutputStream fos = new FileOutputStream("test_copy.jpg");
    // 2.讀寫資料
    // 2.1 定義陣列
    byte[] b = new byte[1024];
    // 2.2 定義長度
    int len;
    // 2.3 迴圈讀取
    while ((len = fis.read(b))!=‐1) {
        // 2.4 寫出資料
        fos.write(b, 0 , len);
    }
    // 3.關閉資源
    fos.close();
    fis.close();
}

注意流的關閉原則:先開後關,後開先關。

1.4 字元流

當使用位元組流讀取文字檔案時,可能會有一個小問題。就是遇到中文字元時,可能不會顯示完整的字元,那是因為 一箇中文字元可能佔用多個位元組儲存。所以Java提供一些字元流類,以字元為單位讀寫資料,專門用於處理文字文 件。

1.4.1 字元輸出流

1.4.1.1 超類 Writer

java.io.Writer 抽象類是表示用於寫出字元流的所有類的超類,將指定的字元資訊寫出到目的地。它定義了位元組 輸出流的基本共性功能方法。

  • public void write(int c) :寫入單個字元。
  • public void write(char[] cbuf) :寫入字元陣列。
  • public abstract void write(char[] cbuf, int off, int len) :寫入字元陣列的某一部分,off為陣列的開始索引,len為寫的字元個數。
  • public void write(String str): 寫入字串。
  • public void write(String str, int off, int len) :寫入字串的某一部分,off為字串的開始索引,len寫的字元個數。
  • public void flush() :重新整理該流的緩衝。
  • public void close() :關閉此流,但要先重新整理它。

1.4.1.2 常用子類 FileWriter

java.io.FileWriter 類是寫出字元到檔案的便利類。構造時使用系統預設的字元編碼和預設位元組緩衝區。

(1)構造方法

  • FileWriter(File file) : 建立一個新的 FileWriter,給定要讀取的File物件。
  • FileWriter(String fileName) : 建立一個新的 FileWriter,給定要讀取的檔案的名稱。

程式碼舉例:

public static void main(String[] args) throws IOException {
    // 使用File物件建立流物件
    File file = new File("a.txt");
    FileWriter fw = new FileWriter(file);
    // 使用檔名稱建立流物件
    FileWriter fw = new FileWriter("b.txt");
}

(2)輸出字元資料到檔案

  • write(int b) 方法,每次可以寫出一個字元資料到輸出流。

程式碼示例:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileWriter fw = new FileWriter("fw.txt");
    // 寫出資料
    fw.write(97); // 寫出第1個字元
    fw.write('b'); // 寫出第2個字元
    fw.write('C'); // 寫出第3個字元
    fw.write(30000); // 寫出第4個字元,中文編碼表中30000對應一個漢字。
    /*
    【注意】關閉資源時,與FileOutputStream不同。
    如果不關閉,資料只是儲存到緩衝區,並未儲存到檔案。
    */
    fw.close();
}
//檢視fw檔案內容為:abC田

(3)輸出字元陣列資料到檔案

  • write(char[] cbuf)write(char[] cbuf, int off, int len) ,每次可以寫出字元陣列中對應長度的資料到輸出流,用法類似FileOutputStream

程式碼示例:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileWriter fw = new FileWriter("fw.txt");
    // 字串轉換為位元組陣列
    char[] chars = "滴答滴答".toCharArray();
    // 寫出字元陣列
    fw.write(chars); // 滴答滴答
    // 寫出從索引2開始,2個位元組。索引2是'滴',兩個位元組,也就是'滴答'。
    fw.write(b,2,2); // 滴答
    // 關閉資源
    fos.close();
}
//檢視檔案fw內容為:滴答

(4)輸出字串資料到檔案

  • write(String str)write(String str, int off, int len) ,每次可以寫出字串中的資料到輸出流,更為方便,

程式碼示例:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileWriter fw = new FileWriter("fw.txt");
    // 寫出字串
    fw.write("滴答"); //滴答
   // 寫出換行
    fw.write("\r\n");
    // 寫出字串
    fw.write("哈哈");
    // 關閉資源
    fos.close();
}

(5)重新整理和關閉輸出流

因為內建緩衝區的原因,如果不關閉輸出流,無法寫出字元到檔案中。但是關閉的流物件,是無法繼續寫出資料 的。如果我們既想寫出資料,又想繼續使用流,就需要 flush 方法了。

  • flush :重新整理緩衝區,流物件可以繼續使用。
  • close :先重新整理緩衝區,然後通知系統釋放資源。流物件不可以再被使用了。

程式碼示例:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileWriter fw = new FileWriter("fw.txt");
    // 寫出資料,通過flush
    fw.write('刷'); // 寫出第1個字元
    fw.flush();
    fw.write('新'); // 繼續寫出第2個字元,寫出成功
    fw.flush();
    // 寫出資料,通過close
    fw.write('關'); // 寫出第1個字元
    fw.close();
    fw.write('閉'); // 繼續寫出第2個字元,【報錯】java.io.IOException: Stream closed
    fw.close();
}

1.4.2 字元輸入流

1.4.2.1 超類 Reader

java.io.Reader 抽象類是表示用於讀取字元流的所有類的超類,可以讀取字元資訊到記憶體中。它定義了字元輸入流的基本共性功能方法。

  • public void close() :關閉此流並釋放與此流相關聯的任何系統資源。
  • public int read() : 從輸入流讀取一個字元。
  • public int read(char[] cbuf) : 從輸入流中讀取一些字元,並將它們儲存到字元陣列 cbuf中 。

1.4.2.2 常用子類 FileReader

java.io.FileReader 類是讀取字元檔案的便利類。構造時使用系統預設的字元編碼和預設位元組緩衝區。

  1. 字元編碼:位元組與字元的對應規則。Windows系統的中文編碼預設是GBK編碼表。 idea中預設編碼是UTF-8 。
  2. 位元組緩衝區:一個位元組陣列,用來臨時儲存位元組資料。

(1)構造方法

  • FileReader(File file) : 建立一個新的 FileReader ,給定要讀取的File物件。
  • FileReader(String fileName) : 建立一個新的 FileReader ,給定要讀取的檔案的名稱。

程式碼示例:

public static void main(String[] args) {
    // 使用File物件建立流物件
    File file = new File("a.txt");
    FileReader fr = new FileReader(file);
    // 使用檔名稱建立流物件
    FileReader fr = new FileReader("b.txt");
}

(2)從檔案讀取字元

  • read 方法,每次可以讀取一個字元的資料,提升為int型別,如讀取到檔案末尾,返回 -1 。

程式碼示例:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileReader fr = new FileReader("read.txt");
    // 定義變數,儲存資料
    int b ;
    // 迴圈讀取
    while ((b = fr.read())!=‐1) {
    	System.out.println((char)b);
    }
    // 關閉資源
    fr.close();
}

(3)從檔案讀取字元陣列

  • read(char[] cbuf) ,每次讀取b.lenght個字元到陣列中,返回讀取到的有效字元個數, 讀取到末尾時,返回 -1 。

程式碼示例:

public static void main(String[] args) throws IOException {
    // 使用檔名稱建立流物件
    FileReader fr = new FileReader("read.txt");
    // 定義變數,儲存有效字元個數
    int len ;
    // 定義字元陣列,作為裝字元資料的容器
    char[] cbuf = new char[2];
    // 迴圈讀取
    while ((len = fr.read(cbuf))!=‐1) {
    	System.out.println(new String(cbuf,0,len));
    }
    // 關閉資源
    fr.close();
}

注意:字元流,只能操作文字檔案,不能操作圖片,視訊等非文字檔案。 當我們單純讀或者寫文字檔案時使用字元流 ,其他情況使用位元組流。

2. 緩衝流

緩衝流,也叫高效流,是對I/O輸入輸出流的增強。

按照資料型別分類:

  • 位元組緩衝流: BufferedInputStreamBufferedOutputStream
  • 字元緩衝流: BufferedReaderBufferedWriter

緩衝流的基本原理,是在建立流物件時,會建立一個內建的預設大小的緩衝區陣列,通過緩衝區讀寫,減少系統IO 次數,從而提高讀寫的效率。

2.1 位元組緩衝流

2.1.1 構造方法

  • public BufferedInputStream(InputStream in) :建立一個新的緩衝輸入流。
  • public BufferedOutputStream(OutputStream out) : 建立一個新的緩衝輸出流。

程式碼示例:

// 建立位元組緩衝輸入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 建立位元組緩衝輸出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

2.2.2 效率測試

查詢API,緩衝流讀寫方法與基本的流是一致的,我們通過複製大檔案(1.5G),測試它的效率。

普通流測試程式碼:

public static void main(String[] args) throws FileNotFoundException {
    // 記錄開始時間
    long start = System.currentTimeMillis();
    // 建立流物件
    try (
        FileInputStream fis = new FileInputStream("D:\\mysoft\\maven.rar");
        FileOutputStream fos = new FileOutputStream("D:\\mysoft\\copy.rar")
    ){
        // 讀寫資料
        int len;
        byte[] bytes = new byte[8*1024];
        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes, 0 , len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 記錄結束時間
    long end = System.currentTimeMillis();
    System.out.println("普通流複製時間:"+(end-start)+" 毫秒");
}
//普通流複製時間:42352 毫秒

緩衝流測試程式碼:

public static void main(String[] args) throws FileNotFoundException {
    // 記錄開始時間
    long start = System.currentTimeMillis();
    // 建立流物件
    try (
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\mysoft\\maven.rar"));
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\mysoft\\copy.rar"));
    ){
        // 讀寫資料
        int len;
        byte[] bytes = new byte[8*1024];
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0 , len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 記錄結束時間
    long end = System.currentTimeMillis();
    System.out.println("緩衝流複製時間:"+(end-start)+" 毫秒");
}
//緩衝流複製時間:1787毫秒

由輸出結果可見,使用緩衝流,能夠大大提高效率。

2.2 字元緩衝流

2.2.1 構造方法

  • public BufferedReader(Reader in) :建立一個 新的緩衝輸入流。
  • public BufferedWriter(Writer out) : 建立一個新的緩衝輸出流。

程式碼示例:

// 建立字元緩衝輸入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 建立字元緩衝輸出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

2.2.2 特有方法

字元緩衝流的基本方法與普通字元流呼叫方式一致,不再闡述,我們來看它們具備的特有方法。

  • BufferedReaderpublic String readLine() : 讀一行文字。
  • BufferedWriterpublic void newLine() : 寫一行行分隔符,由系統屬性定義符號。

newLine方法程式碼演示:

public static void main(String[] args) throws IOException {
    // 建立流物件
    BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
    // 寫出資料
    bw.write("測試換行1");
    // 寫出換行
    bw.newLine();
    bw.write("測試換行2");
    bw.newLine();
    bw.write("測試換行3");
    bw.newLine();
    // 釋放資源
    bw.close();
}

readLine方法示例程式碼:

public static void main(String[] args) throws IOException {
    // 建立流物件
    BufferedReader br = new BufferedReader(new FileReader("out.txt"));
    // 定義字串,儲存讀取的一行文字
    String line = null;
    // 迴圈讀取,讀取到最後返回null
    while ((line = br.readLine())!=null) {
        System.out.print(line);
        System.out.println("‐‐‐‐‐‐");
    }
    // 釋放資源
    br.close();
}

3. 轉換流

3.1 字元編碼和字符集

(1)字元編碼

  • 字元編碼 Character Encoding : 就是一套自然語言的字元與二進位制數之間的對應規則。

計算機中儲存的資訊都是用二進位制數表示的,而我們在螢幕上看到的數字、英文、標點符號、漢字等字元是二進位制數轉換之後的結果。按照某種規則,將字元儲存到計算機中,稱為編碼 。反之,將儲存在計算機中的二進位制數按照 某種規則解析顯示出來,稱為解碼 。

比如說,按照A規則儲存,同樣按照A規則解析,那麼就能顯示正確的文字符號。反之,按照A規則儲存,再按照B規則解析,就會導致亂碼現象。

(2)字符集

  • 字符集 Charset :也叫編碼表。是一個系統支援的所有字元的集合,包括各國家文字、標點符號、圖形符 號、數字等。

計算機要準確的儲存和識別各種字符集符號,需要進行字元編碼,一套字符集必然至少有一套字元編碼。常見字符集有ASCII字符集、GBK字符集、Unicode字符集(UTF8、UTF16、UTF32)等。

3.2 編碼引出的問題

在IDEA中,使用 FileReader 讀取專案中的文字檔案。由於IDEA的設定,都是預設的 UTF-8 編碼,所以沒有任何問題。但是,當讀取Windows系統中建立的文字檔案時,由於Windows系統的預設是GBK編碼,就會出現亂碼。

public static void main(String[] args) throws IOException {
    FileReader fileReader = new FileReader("D:\\GBK.txt");
    int read;
    while ((read = fileReader.read()) != ‐1) {
    System.out.print((char)read);
    }
    fileReader.close();
}
//輸出結果:��Һ�

那麼如何讀取GBK編碼的檔案呢?這時就需要用到轉換流了。

3.3 InputStreamReader類

轉換流 java.io.InputStreamReader ,是Reader的子類,是從位元組流到字元流的橋樑。它讀取位元組,並使用指定的字符集將其解碼為字元。它的字符集可以由名稱指定,也可以接受平臺的預設字符集。

3.3.1 構造方法

  • InputStreamReader(InputStream in) : 建立一個使用預設字符集的字元流。
  • InputStreamReader(InputStream in, String charsetName) : 建立一個指定字符集的字元流。

程式碼示例:

InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");

3.3.3 指定編碼讀取檔案

public static void main(String[] args) throws IOException {
    // 定義檔案路徑,檔案為gbk編碼
    String FileName = "D:\\GBK.txt";
    // 建立流物件,預設UTF8編碼
    InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
    // 建立流物件,指定GBK編碼
    InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");
    // 定義變數,儲存字元
    int read;
    // 使用預設編碼字元流讀取,亂碼
    while ((read = isr.read()) != ‐1) {
        System.out.print((char)read); // ��Һ�
    }
    isr.close();
    // 使用指定編碼字元流讀取,正常解析
    while ((read = isr2.read()) != ‐1) {
        System.out.print((char)read);// 大家好
    }
    isr2.close();
}

3.4 OutputStreamWriter類

轉換流 java.io.OutputStreamWriter ,是Writer的子類,是從字元流到位元組流的橋樑。使用指定的字符集將字元編碼為位元組。它的字符集可以由名稱指定,也可以接受平臺的預設字符集。

3.4.1 構造方法

  • OutputStreamWriter(OutputStream in) : 建立一個使用預設字符集的字元流。
  • OutputStreamWriter(OutputStream in, String charsetName) : 建立一個指定字符集的字元流。

程式碼示例:

OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");

3.4.2 按指定編碼寫出到檔案

public static void main(String[] args) throws IOException {
    // 定義檔案路徑
    String FileName = "E:\\out.txt";
    // 建立流物件,預設UTF8編碼
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
    // 寫出資料
    osw.write("你好"); // 儲存為6個位元組
    osw.close();
    // 定義檔案路徑
    String FileName2 = "E:\\out2.txt";
    // 建立流物件,指定GBK編碼
    OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
    // 寫出資料
    osw2.write("你好");// 儲存為4個位元組
    osw2.close();
}

3.5 轉換檔案編碼

將GBK編碼的文字檔案,轉換為UTF-8編碼的文字檔案。

步驟:

  1. 指定GBK編碼的轉換流,讀取文字檔案。
  2. 使用UTF-8編碼的轉換流,寫出文字檔案。

程式碼示例:

public static void main(String[] args) {
    // 1.定義檔案路徑
    String srcFile = "file_gbk.txt";
    String destFile = "file_utf8.txt";
    // 2.建立流物件
    // 2.1 轉換輸入流,指定GBK編碼
    InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
    // 2.2 轉換輸出流,預設utf8編碼
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
    // 3.讀寫資料
    // 3.1 定義陣列
    char[] cbuf = new char[1024];
    // 3.2 定義長度
    int len;
    // 3.3 迴圈讀取
    while ((len = isr.read(cbuf))!=‐1) {
        // 迴圈寫出
        osw.write(cbuf,0,len);
    }
    // 4.釋放資源
    osw.close();
    isr.close();
}

4. 序列化流

4.1 概述

Java 提供了一種物件序列化的機制。用一個位元組序列可以表示一個物件,該位元組序列包含該物件的資料 、物件的型別和物件中儲存的屬性等資訊。

位元組序列寫出到檔案之後,相當於檔案中持久儲存了一個物件的資訊。 反之,該位元組序列還可以從檔案中讀取回來,重構物件,對它進行反序列化。物件的資料 、物件的型別和物件中儲存的資料 資訊,都可以用來在記憶體中建立物件。

如圖所示:

4.2 ObjectOutputStream類(序列化)

java.io.ObjectOutputStream 類,將Java物件的原始資料型別寫出到檔案,實現物件的持久儲存。

4.2.1 構造方法

  • public ObjectOutputStream(OutputStream out) : 建立一個指定OutputStream的ObjectOutputStream。

程式碼示例:

FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

4.2.2 序列化操作

一個物件要想序列化,必須滿足兩個條件:

  • 該類必須實現 java.io.Serializable 介面, Serializable 是一個標記介面,不實現此介面的類將不會使任何狀態序列化或反序列化,會丟擲 NotSerializableException 。
  • 該類的所有屬性必須是可序列化的。如果有一個屬性不需要可序列化的,則該屬性必須註明是瞬態的,使用 transient 關鍵字修飾。
public class Employee implements java.io.Serializable {
    public String name;
    public String address;
    public transient int age; // transient瞬態修飾成員,不會被序列化
    public void addressCheck() {
    	System.out.println("Address check : " + name + " ‐‐ " + address);
    }
}

序列化物件:

  • public final void writeObject (Object obj) : 將指定的物件寫出到檔案。
public static void main(String [] args) {
    Employee e = new Employee();
    e.name = "zhangsan";
    e.address = "beiqinglu";
    e.age = 20;
    try {
        // 建立序列化流物件
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
        // 序列化物件到檔案
        out.writeObject(e);
        // 釋放資源
        out.close();
        fileOut.close();
        System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年齡沒有被序列化。
    } catch(IOException i) {
        i.printStackTrace();
    }
}

4.3 ObjectInputStream類(反序列化)

ObjectInputStream反序列化流,將之前使用ObjectOutputStream序列化的原始資料恢復為物件。

4.3.1 構造方法

  • public ObjectInputStream(InputStream in) : 建立一個指定InputStreamObjectInputStream

4.3.2 反序列化

如果能找到一個物件的class檔案,我們可以進行反序列化操作,呼叫 ObjectInputStream 讀取物件的方法:readObject ()

示例程式碼:

public static void main(String [] args) {
    Employee e = null;
    try {
        // 建立反序列化流
        FileInputStream fileIn = new FileInputStream("employee.txt");
        ObjectInputStream in = new ObjectInputStream(fileIn);
        // 讀取一個物件
        e = (Employee) in.readObject();
        // 釋放資源
        in.close();
        fileIn.close();
    }catch(IOException i) {
        // 捕獲其他異常
        i.printStackTrace();
        return;
    }catch(ClassNotFoundException c) {
        // 捕獲類找不到異常
        System.out.println("Employee class not found");
        c.printStackTrace();
        return;
    }
    // 無異常,直接列印輸出
    System.out.println("Name: " + e.name);  
    System.out.println("Address: " + e.address); 
    System.out.println("age: " + e.age); 
}

另外,當JVM反序列化物件時,能找到class檔案,但是class檔案在序列化物件之後發生了修改,那麼反序列化操 作也會失敗,丟擲一個 InvalidClassException 異常。

發生這個異常的原因如下:

  • 該類的序列版本號與從流中讀取的類描述符的版本號不匹配
  • 該類包含未知資料型別
  • 該類沒有可訪問的無引數構造方法

Serializable 介面給需要序列化的類,提供了一個序列版本號。 serialVersionUID 該版本號的目的在於驗證序列化的物件和對應類是否版本匹配。

public class Employee implements java.io.Serializable {
    // 加入序列版本號
    private static final long serialVersionUID = 1L;
    public String name;
    public String address;
    // 新增新的屬性 ,重新編譯, 可以反序列化,該屬性賦為預設值.
    public int eid;
    public void addressCheck() {
        System.out.println("Address check : " + name + " ‐‐ " + address);
    }
}

5. 列印流

5.1 概述

平時我們在控制檯列印輸出,是呼叫 print 方法和 println 方法完成的,這兩個方法都來自於 java.io.PrintStream 類,該類能夠方便地列印各種資料型別的值,是一種便捷的輸出方式。

5.2 PrintStream類

5.2.1 構造方法

public PrintStream(String fileName) : 使用指定的檔名建立一個新的列印流。

PrintStream ps = new PrintStream("ps.txt");

5.2.2 改變列印流向

System.out 就是 PrintStream 型別的,它的流向是系統規定的,列印在控制檯上。不過,既然是流物件, 我們就可以玩一個"小把戲",改變它的流向。

public static void main(String[] args) throws IOException {
    // 呼叫系統的列印流,控制檯直接輸出97
    System.out.println(97);
    // 建立列印流,指定檔案的名稱
    PrintStream ps = new PrintStream("ps.txt");
    // 設定系統的列印流流向,輸出到ps.txt
    System.setOut(ps);
    // 呼叫系統的列印流,ps.txt中輸出97
    System.out.println(97);
}

6. 屬性集Properties

6.1 概述

java.util.Properties 繼承於 Hashtable ,來表示一個持久的屬性集。它使用鍵值結構儲存資料,每個鍵及其 對應值都是一個字串。該類也被許多Java類使用,比如獲取系統屬性時, System.getProperties 方法就是返回 一個 Properties 物件。

6.2 Properties類

6.2.1 構造方法

  • public Properties() :建立一個空的屬性列表。

6.2.2 常用方法

  • public Object setProperty(String key, String value) : 儲存一對屬性。
  • public String getProperty(String key) :使用此屬性列表中指定的鍵搜尋屬性值。
  • public Set stringPropertyNames() :所有鍵的名稱的集合。

示例程式碼:

public static void main(String[] args) throws FileNotFoundException {
    // 建立屬性集物件
    Properties properties = new Properties();
    // 新增鍵值對元素
    properties.setProperty("filename", "a.txt");
    properties.setProperty("length", "209385038");
    properties.setProperty("location", "D:\\a.txt");
    // 列印屬性集物件
    System.out.println(properties);
    // 通過鍵,獲取屬性值
    System.out.println(properties.getProperty("filename"));
    System.out.println(properties.getProperty("length"));
    System.out.println(properties.getProperty("location"));
    // 遍歷屬性集,獲取所有鍵的集合
    Set<String> strings = properties.stringPropertyNames();
    // 列印鍵值對
    for (String key : strings ) {
        System.out.println(key+" ‐‐ "+properties.getProperty(key));
    }
}

輸出結果:

輸出結果:
{filename=a.txt, length=209385038, location=D:\a.txt}
a.txt
209385038
D:\a.txt
filename ‐‐ a.txt
length ‐‐ 209385038
location ‐‐ D:\a.txt

6.2.3 從流中讀取鍵值對

  • public void load(InputStream inStream) : 從位元組輸入流中讀取鍵值對。

引數中使用了位元組輸入流,通過流物件,可以關聯到某檔案上,這樣就能夠載入文字中的資料了。

文字資料格式:

filename=a.txt
length=209385038
location=D:\a.txt

程式碼演示:

public static void main(String[] args) throws FileNotFoundException {
    // 建立屬性集物件
    Properties pro = new Properties();
    // 載入文字中資訊到屬性集
    pro.load(new FileInputStream("read.txt"));
    // 遍歷集合並列印
    Set<String> strings = pro.stringPropertyNames();
    for (String key : strings ) {
        System.out.println(key+" ‐‐ "+pro.getProperty(key));
    }
}

輸出結果:

filename ‐‐ a.txt
length ‐‐ 209385038
location ‐‐ D:\a.txt

7. IO異常處理最新方式

在使用IO流讀寫資料時,我們一直把異常丟擲,而實際開發中並不能這樣處理,建議使用 try...catch...finally 程式碼塊,處理異常部分。

public static void main(String[] args) {
    // 宣告變數
    FileWriter fw = null;
    try {
        //建立流物件
        fw = new FileWriter("fw.txt");
        // 寫出資料
        fw.write("資料"); 
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (fw != null) {
                fw.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

而JDK7優化成try-with-resource 語句,該語句確保了每個資源在語句結束時關閉。所謂的資源 (resource)是指在程式完成後,必須關閉的物件。

使用格式為:

try (建立流物件語句,如果多個,使用';'隔開) {
	// 讀寫資料
} catch (IOException e) {
	e.printStackTrace();
}
//程式碼演示:
public static void main(String[] args) {
    // 建立流物件
    try ( FileWriter fw = new FileWriter("fw.txt"); ) {
        // 寫出資料
        fw.write("資料"); 
    } catch (IOException e) {
        e.printStackTrace();
    }
}

JDK9中加強了 try-with-resource 的改進,對於引入物件的方式,支援的更加簡潔。

public static void main(String[] args) throws IOException {
    // 建立流物件
    final FileReader fr = new FileReader("in.txt");
    FileWriter fw = new FileWriter("out.txt");
    // 引入到try中
    try (fr; fw) {
        // 定義變數
        int b;
        // 讀取資料
        while ((b = fr.read())!=‐1) {
            // 寫出資料
            fw.write(b);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}