在Java中,把不同的輸入/輸出源(鍵盤、檔案、網路連線等)中的有序資料抽象為流(stream)。
stream(流)是從起源(source)到接收(sink)的有序資料。
通過流的方式,Java可以使用相同的方式來訪問、操作不同型別的輸入/輸出源,不管輸入、輸出節點是磁碟檔案、網路連線,還是其他的IO裝置,只要將這些節點包裝成流,我們就可以使用相同的方式來進行輸入、輸出操作。
原本進行檔案讀寫要用檔案讀寫的一套方法,進行網路讀寫要用網路讀寫的一套方法.....不同型別的IO節點,用的方法體系不同,要單獨寫程式碼。
java統一了不同型別節點的IO操作,都把輸入、輸出節點包裝成流,使用一套方法就可以進行檔案讀寫、網路讀寫等不同型別節點的讀寫,不必單獨寫程式碼,從而簡化了IO操作。
流的分類:
1、按流的方向分:
- 輸入流:從磁碟、網路等讀取到記憶體,只能讀,不能寫。
- 輸出流:從記憶體寫出到磁碟、網路,只能寫,不能讀。
2、按操作的資料單元分:
- 位元組流:以位元組為基本操作單位
- 字元流:以字元為基本操作單位
位元組流、字元流的用法基本一樣,只是操作的資料單元不同。
3、按照流的角色來分:
- 節點流:向某個IO裝置/節點(磁碟檔案、網路等)直接讀寫資料,也稱為低階流。
- 處理流:用於包裝一個已存在的流,通過這個已存在的流來進行讀寫操作,並不直接操作IO節點,也稱為高階流、包裝流。
處理流是一種典型的裝飾器設計模式,通過使用處理流來包裝不同的節點流,可以消除不同節點流的實現差異,訪問不同的資料來源。
InputStream是位元組輸入流的頂級父類,常用方法:
- int read() //讀取一個位元組,返回該位元組資料的Unicode碼值
- int read(byte[] buff) //最多讀取buff.length個位元組,將讀取的資料放在buff陣列中,返回實際讀取的位元組數
- int read(byte[] buff, int off, int length) //最多讀取length個位元組,放在buff陣列中,從陣列的off位置開始放置資料,返回實際讀取的位元組數。off一般設定為0,length一般設定為buff的長度(這樣其實和第二個函式的功能完全一樣)。
Reader時字元輸入流的頂級父類,常用方法:
- int read() //讀取一個字元,返回該字元的Unicode碼值,注意並不是返回該字元。
- int read(char[] buff) //最多讀取buff.length個字元,放在buff陣列中,返回實際讀取的字元數
- int read(char[] buff, int off, int length) //最多讀取length個位元組,放在buff陣列中,從陣列的off位置開始放置資料,返回實際讀取的字元數。off一般設定為0,length一般設定為buff的長度(這樣其實和第二個函式的功能完全一樣)。
位元組流和字元流的用法差不多,只是操作的資料單元不同。
如果已讀完(沒有資料可讀),以上方法均返回 -1 。
InputStream、Reader都是抽象類,不能直接建立示例,要用已實現的子類建立例項,比如FileInputStream類、FileReader類。
示例:
1 FileInputStream fis=new FileInputStream("./1.txt"); 2 /* 3 因為是以位元組為單位操作的,如果要將byte[]轉換為String,中文、byte[]長度設定不對,就可能讀取到中文字元的半個位元組,最終得到的String會亂碼。 4 有2種解決中文亂碼的方式: 5 1、將陣列長度設定為較大的值(大於等於輸入流的內容長度) 6 2、如果輸入節點以GBK、GB2312編碼(2位元組碼),將陣列長度設定為2的倍數;如果輸入節點以UTF-8編碼(3位元組碼),將陣列長度設定為3的倍數 7 注意:在Windows的資源管理器中新建文字檔案,預設字符集是ASCII,儲存時注意修改編碼方式。 8 */ 9 byte[] buff=new byte[1024]; 10 int length=0; 11 while ((length=fis.read(buff))!=-1){ 12 //使用String的建構函式將byte[]轉化為String。此處用new String(buff)也是一樣的 13 System.out.println(new String(buff,0,length)); 14 } 15 //使用完,要關閉流 16 fis.close();
1 FileReader fr=new FileReader("./1.txt"); 2 //因為操作單位是一個字元,所以陣列大小設定為多少都行,不會亂碼。 3 char[] buff=new char[1024]; 4 while (fr.read(buff)!=-1){ 5 System.out.println(new String(buff)); 6 } 7 fr.close();
InputStream、Reader均提供了移動記錄指標的方法:
- long skip(long n) //指標向前移動n個位元組/字元
- void mark(int maxReadLimit) //在當前指標處作一個標記。引數指定此標記的有效範圍,mark()做標記後,再往後讀取maxReadLimte位元組,此標記就失效。
實際上,並不完全由maxReadLimte引數決定,也和BufferedInputStream類的緩衝區大小有關。只要緩衝區夠大,mark()後讀取的資料沒有超出緩衝區的大小,mark標記就不會失效。如果不夠大,mark後又讀取了大量的資料,導致緩衝區更新,原來標記的位置自然找不到了。
- void reset() 將指標重置到上一個mark()標記的位置
- boolean markSupported() //檢查這個流是否支援mark()操作
以上方法只能用於輸入流,不能用於輸出流。
OutputStream是位元組輸出流的頂級父類,常用方法:
- void write(int i) \\輸出一個位元組,i是碼值,即read()得到的碼值,輸出i對應的位元組
- void write(byte[] buff) //輸出整個位元組陣列的內容
- void write(byte[] buff, int off, int length) //把位元組陣列從off位置開始,輸出長度為length位元組的內容
Writer是字元輸出流的頂級父類,常用方法:
- void write(int i) //輸出一個字元,i是碼值,輸出的是i對應的字元
- void write(char[] buff) //輸出整個char[]的內容
- void write(char[] buff, int off, int length) //把char[]從off位置開始,輸出長度為length字元的內容
可以用String代替char[],所以Writer還具有以下2個方法:
- void write(String str)
- void write(String str, int off, int length)
OutputStream、Writer是抽象類,不能例項化,要建立物件,需使用已實現的子類,比如FileOutputStream、FileWriter。
示例:
1 //預設是覆蓋 2 FileOutputStream fos=new FileOutputStream("./2.txt"); 3 fos.write("你好!\n".getBytes()); 4 fos.close(); 5 6 //可指定是否是追加模式,true——是追加模式,false——不是追加模式(即覆蓋) 7 fos=new FileOutputStream("./2.txt",true); 8 fos.write("我是chenhongyong。".getBytes()); 9 fos.close();
1 //預設是覆蓋 2 FileWriter fw=new FileWriter("./2.txt"); 3 fw.write("hello\n"); 4 fw.close(); 5 6 //追加 7 fw=new FileWriter("./2.txt",true); 8 fw.write("world!"); 9 fw.close();
1 //如果輸出流的檔案物件不存在,會報錯 2 FileInputStream fis=new FileInputStream("./1.txt"); 3 //如果輸出流的檔案不存在,會自動建立 4 FileOutputStream fos=new FileOutputStream("./2.txt"); 5 byte[] buff=new byte[1024]; 6 //檔案複製 7 while (fis.read(buff)!=-1) 8 fos.write(buff); 9 fis.close(); 10 fos.close();
Windows的換行符是\r\n,Linux、Unix的換行符是\n。最好用\n,\n是所有OS通用的換行符。
計算機中的檔案可以分為二進位制檔案、文字檔案,能用記事本開啟、並可以看到字元內容的檔案就是文字檔案,反之稱為二進位制檔案。
實際上,從底層來看,計算機上的所有檔案都是基於二進位制儲存、讀寫的。計算機上的檔案都是二進位制檔案,文字檔案是一種特殊的二進位制檔案。
位元組流的功能更強大,因為位元組流可以操作所有的二進位制檔案(計算機上的所有檔案)。
但如果用位元組流處理文字檔案,要進行字元、位元組之間的轉換,要麻煩一些。
所以一般情況下,我們用字元流處理文字檔案,用位元組流處理二進位制檔案。
上面使用的FileInputStream、FileWriter、FileOutputStream、FileWriter都是直接操作IO節點(磁碟檔案),建構函式裡是IO節點,它們都是節點流(低階流)。
處理流用來包裝一個已存在的流,示例:
1 FileOutputStream fos=new FileOutputStream("./1.txt"); 2 //以一個已存在的流物件作為引數 3 PrintStream ps=new PrintStream(fos); 4 5 //可以位元組為單位進行IO操作 6 ps.write("你好!".getBytes()); 7 //可以字元為單位進行IO操作 8 ps.print("很高興認識你!"); 9 //println()輸出後直接換行,不用我們在寫\n,更加簡便 10 ps.println("我是chenhongyong。"); 11 //可輸出各種型別的資料 12 ps.write(12); 13 ps.print(12); 14 ps.print(12.34); 15 16 //關閉處理流時,會自動關閉其包裝的節點流,程式碼更簡潔。 17 ps.close(); 18 19 //處理流提供了更多的輸入/輸出方法,更加簡單、強大
PrintStream類的輸出功能很強大,可以把輸出的低階流包裝成PrintStream使用。
處理流的優點:
- 更簡單、更強大
- 執行效率更高
處理流之緩衝流:
前面提到可以用byte[]、char[]做緩衝區,提高讀寫效率。Java提供了緩衝流,來實現相同的效果,使用方式基本相同。
1 //以相應的節點流物件作為引數 2 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("./1.txt")); 3 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("./2.txt")); 4 //檔案複製 5 int i = 0; 6 while ((i = bis.read()) != -1){ //length是讀取到的內容的碼值,不是讀取到的內容的長度。一次只讀一個位元組。 7 System.out.println(length); 8 bos.write(length); //將讀取的資料寫到輸出流 9 } 10 //關閉處理流即可,會自動關閉對應的節點流 11 bis.close(); 12 bos.close();
1 BufferedInputStream bis=new BufferedInputStream(new FileInputStream("./1.txt")); 2 BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("./2.txt")); 3 byte[] buff=new byte[1024]; 4 int length=0; 5 while ((length=bis.read(buff))!=-1){ //length是讀取到的內容的長度 6 System.out.println(length); 7 bos.write(buff); 8 } 9 bis.close(); 10 bos.close();
緩衝區:
計算機訪問外部裝置或檔案,要比直接訪問記憶體慢的多。如果我們每次呼叫read()方法或者writer()方法訪問外部的裝置或檔案,CPU就要花上最多的時間是在等外部裝置響應,而不是資料處理。
為此,我們開闢一個記憶體緩衝區的記憶體區域,程式每次呼叫read()方法或writer()方法都是讀寫在這個緩衝區中。當這個緩衝區被裝滿後,系統才將這個緩衝區的內容一次集中寫到外部裝置或讀取進來給CPU。使用緩衝區可以有效的提高CPU的使用率,能提高讀寫效率。
緩衝流:
讀寫資料時,讓資料在緩緩衝區能減少系統實際對原始資料來源的存取次數,因為一次能做多個資料單位的操作,相較而言,對於從檔案讀取資料或將資料寫入檔案,比起緩衝區的讀寫要慢多了。所以使用緩衝區的 流,一般都會比沒有緩衝區的流效率更高,擁有緩衝區的流別稱為緩衝流。緩衝流本身沒有輸入、輸出功能,它只是在普通IO流上加一個緩衝區。
緩衝流是和4級頂級父類對應的:加字首Buffered
InputStream BufferedInputStream 位元組輸入緩衝流,可作為所有位元組輸入流類的緩衝流
OutputStream BufferedOutputStream 位元組輸出緩衝流
Reader BufferedReader 字元輸入緩衝流
Writer BufferedWriter 字元輸出緩衝流
執行效率:
普通IO流,逐個位元組/字元操作,效率極低。緩衝流自帶緩衝區,就算是逐個位元組/字元操作,效率也是很高的。
普通IO流,用陣列作為緩衝區,效率極高。緩衝流使用陣列,效率極高。二者實現方式是相同的。
大致上:使用陣列的緩衝流 ≈ 使用陣列的普通流 >> 逐個位元組/字元操作的緩衝流 >> 逐個位元組/字元操作的普通IO流。
儘量不要逐個位元組/字元操作,太慢了。
由於使用了緩衝區,寫的資料不會立刻寫到物理節點,而是會寫到緩衝區,緩衝區滿了才會把緩衝區的資料一次性寫到物理節點。
可以使用流物件的flush()方法,強制刷出緩衝區的資料到物理節點。
當然,呼叫close()關閉流物件時,會先自動呼叫flush()刷出緩衝區的資料到物理節點。
處理流之轉換流:Java提供了2個轉化流,用於將位元組流轉換為字元流。
1、InputStreamReader類,顧名思義,是從InputStream到Reader。InputStreamReader是Reader的子類,操作的資料單元是字元。
InputStreamReader isr=new InputStreamReader(InputStream is); //引數是InputStream類的物件
2、OutputStreamWriter類,顧名思義,是從OutputStream到Writer。OutputStreamWriter是Writer的子類,操作的資料單元是字元。
OutputStreamWriter isr=new OutputStreamWriter(OutputStream os); //引數是OutputStream類的物件
因為字元流本就比較方便,所以沒有將字元流轉換為位元組流的類。
預定義的標準流物件:
- System.in 標準輸入流物件(一般指鍵盤輸入),是InputStream的一個物件,可使用InputStream的一切方法。
- System.out 標準輸出流物件(一般指顯示器),是OutputStream的一個物件
- System.err 標準錯誤輸出流。
1 byte[] buff=new byte[100]; 2 //將鍵盤輸入(從控制檯輸入)讀取到byte[]中 3 System.in.read(buff); 4 //轉換為String輸出 5 System.out.println(new String(buff));
重定向標準流物件:
- System.setIn(InputStream is); //將標準輸入流切換到指定的InputStream物件
- System.setOut(PrintStream ps);
- System.setErr(PrintStream ps);
1 PrintStream ps=new PrintStream("./err.txt"); 2 //重定向標準錯誤輸出流,錯誤資訊不再輸出到控制檯,而是輸出到err.txt檔案 3 System.setErr(ps); 4 int a=4/0;
1 PrintStream ps=new PrintStream("./out.txt"); 2 //重定向標準輸出流,標準輸出不再輸出到螢幕(控制檯),而是輸出到out.txt檔案 3 System.setOut(ps); 4 //1會輸出到out.txt檔案 5 System.out.println(1);
Java常用的流:
分類 | 位元組輸入流 | 位元組輸出流 | 字元輸入流 | 字元輸出流 |
抽象基類 | InputStream | OutputStream | Reader | Writer |
操作檔案 | FileInputStream | FileOutputStream | FileReader | FileWriter |
運算元組 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
操作字串 | StringReader | StringWriter | ||
緩衝流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
轉換流 | InputStreamReader | OutputStreamWriter | ||
物件流(用於序列化) | ObjectInputStream | ObjectOutputStream | ||
抽象基類(用於過濾) | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
列印流(輸出功能極其大) | PrintStream(實際上也可用於輸出字元) | PrintWriter(不能輸出byte) |
部分流可以在建構函式中以引數String charset或Charset charset的形式指定字符集。
此外,還有其他的IO流,比如:
- AudioInputStream、AudioOutputStream 音訊輸入、輸出
- CipherInputStream、CipherOutputStream 加密、解密
- DeflaterInputStream、ZipInputStream、ZipOutputStream 檔案壓縮、解壓
後一篇隨筆再介紹。