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字元
注意:
- 雖然引數為int型別四個位元組,但是隻會保留一個位元組的資訊寫出。
- 流操作完畢後,必須呼叫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
類是讀取字元檔案的便利類。構造時使用系統預設的字元編碼和預設位元組緩衝區。
- 字元編碼:位元組與字元的對應規則。Windows系統的中文編碼預設是GBK編碼表。 idea中預設編碼是UTF-8 。
- 位元組緩衝區:一個位元組陣列,用來臨時儲存位元組資料。
(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輸入輸出流的增強。
按照資料型別分類:
- 位元組緩衝流:
BufferedInputStream
,BufferedOutputStream
- 字元緩衝流:
BufferedReader
,BufferedWriter
緩衝流的基本原理,是在建立流物件時,會建立一個內建的預設大小的緩衝區陣列,通過緩衝區讀寫,減少系統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 特有方法
字元緩衝流的基本方法與普通字元流呼叫方式一致,不再闡述,我們來看它們具備的特有方法。
BufferedReader
:public String readLine()
: 讀一行文字。BufferedWriter
:public 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編碼的文字檔案。
步驟:
- 指定GBK編碼的轉換流,讀取文字檔案。
- 使用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)
: 建立一個指定InputStream
的ObjectInputStream
。
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();
}
}