九、字節流與字符流
9.1 IO的分類
<段落>根據數據的流向分為:輸入流和輸出流。數據的類型分為:字節流和字符流。
- 輸入流 :把數據從其他設備上讀取到內存中的流。
- 輸出流 :把數據從內存 中寫出到其他設備上的流。
- 字節流 :以字節為單位,讀寫數據的流。
- 字符流 :以字符為單位,讀寫數據的流。
頂級父類表:
輸入流 輸出流 字節流 字節輸入流 InputStream 字節輸出流 OutputStream 字符流 字符輸入流 Reader 字符輸出流Writer 9.2字節流輸出流
9.2.1 概述:
一切文件數據都是以二進制數字的形式保存,我們要時刻明確,無論使用什么樣的流對象,底層傳輸始終為二進制數據。9.2.2 字節輸出流的構造方法:
OutputStream有很多子類,我們從最簡單的一個子類開始。
java.io.FileOutputStream類是文件輸出流,用於將數據寫出到文件。
構造方法
public FileOutputStream(File file):創建文件輸出流以寫入由指定的 File對象表示的文件。notes: 在進行構造的時候,如果父目錄不存在,則會報錯,在創建時 父目錄存在,但是文件不存在,則會幫你創建。
public FileOutputStream(String name): 創建文件輸出流以指定的名稱寫入文件
notes:
當你創建一個流對象時,必須傳入一個文件路徑。該路徑下,如果沒有這個文件,會創建該文件。如果有這個文件,會清空這個文件的數據。
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
// 創建 File 對象
File file = new File("a.txt");
// 創建文件輸出流對象
FileOutputStream fos = new FileOutputStream(file);
// 使用文件 名稱創建 輸出流對象
FileOutputStream fos1 = new FileOutputStream("a.txt");
fos.close();
fos1.close();
}
9.2.3字節輸出流的基本共性方法:
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):將指定的字節輸出流。
tipps:
close方法,當完成流的操作時,必須調用此方法,釋放系統資源。
<wiz_code_mirror>
public static void main(String[] args) throws IOException{
// 在 寫的時候 是 將原來的刪除 然后再去 寫
FileOutputStream fos = new FileOutputStream("fos.txt");
// 寫 數據
fos.write(97);
fos.write(98);
//根據 字節數組 寫數據
byte[] b = {'1','2','3','4'};
fos.write(b);
// 將指定 段 數據寫入
// write(byte[] b, int off, int len) ,每次寫出從off索引開始,len個字節
fos.write(b,0,2);
// 關閉
fos.close();
}
-->輸出結果:
當沒有fos.txt文件時,寫入ab123412
有則將其覆蓋
tips:
1.雖然 參數 為 int 類型 四個字節 但是 只會保留一個字節的信息寫出2.流操作完畢后,必須釋放系統資源,調用close方法
數據追加續寫: (只需要在 原來的 構造方法 后再加 一個參數 ,true表示 續寫,false 表示清空數據)
每次都會 清空目標文件中的數據,如何保留目標文件中數據,還能繼續添加數據
構造方法:
寫出換行:
public FileOutputStream(File file, boolean append): 創建文件輸出流以寫入由指定的 File對象表示的文件。
public FileOutputStream(String name, boolean append)
\r 回車符 回到一行的開頭 \n 換行符另起一行
系統中
Windows 系統里 換行符合為 \r\nUnix系統里 沒行結尾只有 換行 即 \n;Mac系統里,每行結尾是 回車,即\r 。從 Mac OS X開始與Linux 統一。
<wiz_code_mirror>
public static void main(String[] args) throws IOException{
// 在 寫的時候 是 將原來的刪除 然后再去 寫
FileOutputStream fos = new FileOutputStream("fos.txt");
// 寫 數據
fos.write(97);
fos.write(98);
fos.write("\r\n".getBytes());
//根據 字節數組 寫數據
byte[] b = {'1','2','3','4'};
fos.write(b);
// 將指定 段 數據寫入
fos.write(b,0,2);
// 關閉
fos.close();
}
結果是:
ab
123412
9.3字節輸入流 InputStream
java.io.InputStream抽象類是表示字節輸入流的所有類的超類,可以讀取字節信息到內存中。它定義了字節輸入流的基本共性功能方法。
public void close():關閉此輸入流並釋放與此流相關聯的任何系統資源。
public abstract int read(): 從輸入流讀取數據的下一個字節。
public int read(byte[] b): 從輸入流中讀取一些字節數,並將它們存儲到字節數組 b中 。
tips:
9.3.1 構造方法
java.io.FileInputStream類是文件輸入流,從文件中讀取字節。構造方法
FileInputStream(File file): 通過打開與實際文件的連接來創建一個 FileInputStream ,該文件由文件系統中的 File對象 file命名。
FileInputStream(String name): 通過打開與實際文件的連接來創建一個 FileInputStream ,該文件由文件系統中的路徑名 name命名。
當你創建一個流對象時,必須傳入一個文件路徑。該路徑下,如果沒有該文件,會拋出
FileNotFoundException
<wiz_code_mirror>
public static void main(String[] args) throws FileNotFoundException {
File file = new File("a.txt");
// 通過構造方法 創建 輸入流對象
FileInputStream fis = new FileInputStream(file);
FileInputStream fis1 = new FileInputStream("a.txt");
}
9.3.2 方法
1.讀取字節:read方法,每次可以讀取一個字節的數據,提升為int類型,當讀取到文件末尾的時候 則會返回 -1 。
可以根據 -1 來判斷是否到達文件末尾:
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
File file = new File("fos.txt");
// 通過構造方法 創建 輸入流對象
FileInputStream fis = new FileInputStream(file);
FileInputStream fis1 = new FileInputStream("fos.txt");
// 提升成 int 類型
int read = fis.read();
System.out.println("read = " + read); // 如果不轉換成 char 類型 則是97 轉成char類型則是 a
int read1 = fis.read();
System.out.println("read = " + read1);
int read2 = fis.read();
System.out.println("read = " + read2);
int read3 = fis.read();
System.out.println("read = " + read3);
// 讀取到文件末尾就返回 -1
fis.close();
fis1.close();
}
2.對循環進行改進 使用while 循環
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
// 創建文件對象
File file = new File("fos.txt");
// 創建 輸入流對象
FileInputStream fis = new FileInputStream(file);
// 通過while 循環 當 b != -1 時 退出
int b;
while ((b = fis.read()) != -1) {
System.out.println("b = " + (char) b);
}
// 關閉流對象
fis.close();
}
tips:1.雖然讀取了一個字節,但是會自動提升 為 int 類型2. 流操作完畢后,必須釋放系統資源, 調用close方法3.使用字節數組讀取 : read(byte[] b), 每次讀取b個長度的字節到數組中,返回讀取到的有效字節個數 讀取到末尾時 返回 -1
<wiz_code_mirror>
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));
}
// 關閉資源
fis.close();
}
tips:使用數組進行讀取,每次讀取多個字節,減少系統見4的IO操作的次數,從而 提高了讀寫的效率,建議開發中使用9.3.3視頻的復制
從已有的文件中讀取字節,將該字節寫出到另一個文件中
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
File file = new File("D:\\111\\shipin.flv");
// 創建 輸入流 將文件讀入 到內存中
FileInputStream fis = new FileInputStream(file);
// 創建輸出流 將數據從內存寫入到 文件中
FileOutputStream fos = new FileOutputStream("D:\\2222\\flower.flv");
int len;
byte[] b = new byte[1024];
// 循環讀取
while ((len = fis.read(b)) != -1) {
// 寫數據
fos.write(b,0,len);
}
fos.close();
fis.close();
}
notes:
流關閉原則: 先開后關,后開先關9.3.4字節緩沖流包裝
構造方法:public BufferInputStream(InputStream in): 創建一個新的緩沖輸入流
public BufferOutputStream(OutputStream out):創建一個新緩沖輸出流
<wiz_code_mirror>
public static void main(String[] args) throws FileNotFoundException {
// 創建 字節緩沖輸入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
// 創建 字節緩沖輸出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));
}
實現一個復制視頻的操作,聯系 BufferInputStream 和 BufferOutputStream
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
// 復制一份視頻 用BuffOutputStream
// 創建一個 輸入流對象
long start = System.currentTimeMillis();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/福利贈送.flv"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("s.flv"));
int len;
byte[] b = new byte[1024*8];
while ((len = bis.read(b)) != -1){
bos.write(b);
bos.flush();
}
bos.close();
long end = System.currentTimeMillis();
System.out.println(end-start);
}
9.4 字符流
當使用字節流讀取文本文件時,可能會遇到一個小問題。 就是遇到中文字符的時候,可能不會顯示完整的字符,那是因為一個中午字符 可能 占用多個字節。所有java提供了一些字符流,以字符為單位讀寫數據,專門用於處理文本文件。9.4.1 字符輸入流【Reader】
java.io.Reader 抽象類是表搜狐用於讀取字符流的所有類的超類,可以讀取字節信息到內存中。它定義了字節輸入流的基本共性功能方法。
public void close():關閉此流並釋放與此流相關聯的任何系統資源。
public int read()
9.4.2 FileReader 類
java.io.FileReader 類是讀取字符文件的類,構造時使用系統默認的字符編碼和默認字節緩沖區。
tips:
1.字符編碼:字節與字符的對應規則。Windows 系統的中午編碼默認是GBK 編碼表
2.字節緩沖區:一個字節數組,用來臨時存儲字節數據。構造方法:
FileReader(File file): 創建一個新的 FileReader ,給定要讀取的File對象。
FileReader(String fileName): 創建一個新的 FileReader ,給定要讀取的文件的名稱。
當你創建一個流對象時,必須傳入一個文件路徑,類似於FileInputStream
<wiz_code_mirror>
public static void main(String[] args) throws FileNotFoundException {
File file = new File("a.txt");
// 創建字符輸入流對象
FileReader fr = new FileReader(file);
// 使用文件名創建流對象
FileReader fr1 = new FileReader("a.txt");
}
基本方法:1.read方法,每次可以讀取一個字符的數據,提升為int類型,讀取到文件末尾,返回 -1 ,循環讀取
問:為什么 返回值是 int 而不是 byte
答:因為字節輸入流可以操作任意類型的文件,比如圖片音頻等,這些文件底層都是二進制形式存儲的,如果每次讀取都返回byte,有可能在讀到中間的時候遇到111111111 那么這111111111是byte類型的-1我們程序要是遇到 -1 就會停止不讀了,后面的數據就讀取不到了 所以選擇 int接受,如果111111111會在前面補上24個0湊足4個字節,那么byte類型的-1 就變成int類型的255了 這樣就可以保證整個數據讀完了,而結束標記的-1就是int類型。
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
// a.txt 文件中 有 abc
File file = new File("a.txt");
// 創建字符輸入流對象
FileReader fr = new FileReader(file);
// 定義遍歷保存數據
int b;
while ((b = fr.read()) != -1) {
System.out.println("b = " + (char)b);
}
fr.close();
}
結果輸出:
a
b
c
/** tips:
1.雖然讀取了一個字符,但是也會自動提升為int 類型 記得關閉流
*/
2.使用字符數組讀取: read(char[] cbuf),每次讀取b個長度字符到數組中,返回讀取到的有效字符個數,讀取到末尾時,返回 -1
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("read.txt");
// 定義遍歷 保存有效字符個數
int len;
// 定義字符數組 作為裝字符數據的容器
char[] buff = new char[2];
// 進行循環讀取
while ((len=fr.read(buff)) != -1){
System.out.println("buff = " + new String(buff));
}
fr.close();
}
結果:
buff = 我是
buff = 愛J
buff = av
buff = a語
buff = 言語
這樣會出現 緩沖區 內容 粘包一樣的問題
優化;
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("read.txt");
// 定義遍歷 保存有效字符個數
int len;
// 定義字符數組 作為裝字符數據的容器
char[] buff = new char[2];
// 進行循環讀取
while ((len=fr.read(buff)) != -1){
System.out.println("buff = " + new String(buff,0,len));
}
fr.close();
}
結果:
buff = 我是
buff = 愛J
buff = av
buff = a語
buff = 言
9.4.3 字符輸出流【Writer】
java.io.Writer 抽象類 是表示用於寫出字符流的所有類的超類,將指定的字符信息寫到 目的地。它定義了字節輸出流的基本共性功能方法。
public abstract void close():關閉此輸出流並釋放與此流相關聯的任何系統資源。public abstract void flush():刷新此輸出流並強制任何緩沖的輸出字符被寫出。public void write(int b):寫出一個字符。
1. FielWriter類
java.io.FileWriter 類是寫出字符到文件的類 ,構造時 使用系統默認的字符編碼和默認字節緩沖區。構造方法:
- FileWriter(File file): 創建一個新的 FileWriter,給定要讀取的File對象。
- FileWriter(String fileName): 創建一個新的 FileWriter,給定要讀取的文件的名稱。
當你創建一個流對象時,必須傳入一個文件路徑,類似於FileOutputStream
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
// 通過文件對象創建 字符輸出流對象
FileWriter fw = new FileWriter(file);
// 通過文件名字創建一個 字符輸出流對象
FileWriter fw2 = new FileWriter("b.txt");
}
基本寫出數據方法:write()
write(char[] buff)
write(char[] buff, int off, int len) 與 FileOutputStream
寫出字符串
write(String str)和 write(String str , int of ,int len)
flush :刷新緩沖區 ,流對象可以繼續使用
close:關閉流 ,釋放系統資源,關閉前會刷新緩沖區
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
// 通過文件對象創建 字符輸出流對象
FileWriter fw = new FileWriter(file);
fw.write(97);
fw.write('b');
fw.write('c');
fw.write(50000); // 中午編碼表中50000對應的一個漢字
fw.close();
}
結果:
abc鱔
notes:
1.雖然參數int 類型是四個字節,但是只會保留一個字符 的信息寫出。
2.沒有調用close方法,數據只是保存到了緩沖區,並未寫出到文件中
3.即便是調用了 flush 方法 寫出了數據,操作的最后還是要 關閉流
續寫和換行 與FileOutputStream 類似
9.4.4 字符緩沖 輸入 輸出流
<wiz_code_mirror>
public static void main(String[] args) throws IOException {
// 創建字符 緩沖 輸入 輸出流
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
// 首先 按照字節進行讀取
int len ;
char[] b = new char[1024];
while ((len = br.read(b)) != -1){
bw.write(b,0,len);
bw.flush();
}
bw.close();
br.close();
}
notes:字符流,只能操作文本文件,不能操作圖片,視頻等非文本文件。
9.4 IO異常的處理
9.4.1 JDK 7 之前處理的方式
我們一直把異常拋出,而實際開發中並不能這樣處理,建議使用try...catch...finally 代碼塊,處理異常部分
<wiz_code_mirror>
public static void main(String[] args) {
// 聲明變量
FileWriter fw = null;
try {
//創建流對象
fw = new FileWriter("fw.txt");
// 寫出數據
fw.write("我愛JAVA"); //我愛JAVA
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
9.4.2 JDK 7 的處理方式
還可以使用JDK7優化后的try-with-resource 語句,該語句確保了每個資源在語句結束時關閉。所謂的資源(resource)是指在程序完成后,必須關閉的對象。
<wiz_code_mirror>
格式:
try (創建流對象語句,如果多個,使用';'隔開) {
// 讀寫數據
} catch (IOException e) {
e.printStackTrace();
}
演示:
public static void main(String[] args) {
// 創建流對象
try ( FileWriter fw = new FileWriter("fw.txt"); ) {
// 寫出數據
fw.write("我愛java"); //我愛java
} catch (IOException e) {
e.printStackTrace();
}
}
9.4.3 JDK9 的改動
JDK9中try-with-resource 的改進,對於引入對象的方式,支持的更加簡潔。被引入的對象,同樣可以自動關閉,無需手動close,我們來了解一下格式。
<wiz_code_mirror>
改進前的格式:
// 被final修飾的對象
final Resource resource1 = new Resource("resource1");
// 普通對象
Resource resource2 = new Resource("resource2");
// 引入方式:創建新的變量保存
try (Resource r1 = resource1;
Resource r2 = resource2) {
// 使用對象
}
改進后的格式:
// 被final修飾的對象
final Resource resource1 = new Resource("resource1");
// 普通對象
Resource resource2 = new Resource("resource2");
// 引入方式:直接引入
try (resource1; resource2) {
// 使用對象
}
演示;'
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();
}
}
