Java Stream(流)的分类, 四大基本流的介绍
上一篇文章已經介紹過什么是流, 以及流的基本概念
http://blog.csdn.net/nvd11/article/details/29917065
本文主要介紹java四大基本流的方法.
一, java Stream的分類
Java 的Steam有很多個子類,? 可以從各個層面可以劃分為一下若干個類
1.1 輸入流和輸出流
上篇文章介紹過, Java里的Stream只有1種方向.
所以我們把從外部設備流向程序的流成為輸入流
反之, 把從程序流向外部設備的流稱為輸出流.
1.2 字符流和字節流
根據數據在Stream里的最小傳輸單位, 我們也可以把流分為兩類
字符流:
????? 最小傳輸單位為1個字符(java里的字符不再用ASCII碼表示,而是用萬國碼, 所以1個字符(char) = 2個字節(byte) = 16bit(位)).
字節流:
????? 最小傳輸單位為1個字節(byte).
它們有1個最大的區別:
就是字符流只能讀寫文本格式的外部設備.
而字節流可以讀寫所有格式的外部設備(例如2進制文件, 多媒體文件等).
因為字節本身是不需編碼和解碼的, 將字節轉換為字符才涉及編碼和解碼的問題.
1.3 節點流和處理流(原始流和包裹流)
Java里的stream還可以嵌套. 按照流的功能還可以分為節點流和處理流
節點流:
????? 也叫原始流, 用于傳輸基本數據的流
處理流:
????? 也叫包裹流, 包裹在節點流之上, 對節點流的數據作進一步處理的流, 處理流的前提是 具有節點流.
????? 處理流可以多重嵌套
如下圖:
二, java 四大基本Stream
上面說過了, Java的流有很多種, 但是基本上都是繼承自四個基本流, 如果弄明白類族里的上層的類.
下面的各種子類也很容易理解.
Java里四個基本流分別是
InputStream : 輸入字節流, 也就是說它既屬于輸入流, 也屬于字節流
OutputStream: 輸出字節流, 既屬于輸出流, 也屬于字節流
Reader: 輸入字符流, 既屬于輸入流, 又屬于字符流
Writer: 輸出字符流, 既屬于輸出流, 又屬于字符流
它們的關系可以用1個表格來表示:
| ?? Reader | ? Writer |
| ? InputStream | ? OutputStream |
這個4個流都是虛擬類, 也就是說它們不能直接被實例化, 下面的代碼都是用它們的子類(文件流)作例子.
三, Reader流及其常用方法.
本文第1個例子用到的FileReader就是繼承自Reader這個流.
Reader流屬于輸入流和字符流, 也就是說Reader流的作用是從外部設備傳輸數據到程序, 而且最小單位是1個字符.
3.1 new Reader()?
首先講下構造方法, 上面這個方法是沒有throws Exception的
但是 其子類的某些重載的構造方法有些會thorws Excepiont
例如 FileReader 的
public FileReader(String?fileName)throws FileNotFoundException則代表我們初始話1個Reader時有必要將其撲捉(放入try{} 字句or throws給上一層函數.
所以在實際編程中,我們建議初始化1個FileReader時利用如下方法:
FileReader fr = null;try{fr = new FileReader("/home/gateman/tmp/build.xml");}catch(FileNotFoundException e){...}關鍵是第一句的 = null; 不要省去, 否則在try{}字句外就不能使用 fr, 會編譯失敗, 提示fr可能未初始化.
加上 = null; 就編譯通過, 在try{} 字句外使用時判斷一下 fr != null 就ok了.
后面介紹 close()方法時會再次提到.
3.2 int read() throws IOException
read()方法可以講是Reader最基礎的一個方法, 它的作用是從流中讀取1個字符, 并把這個字符存儲到1個整形(int)變量中.
如果讀到了輸入流的末尾(最后1個字符) 則返回 -1.
我們知道字符類型是char, 為何返回的是1個int類型?
原因就是為了接口統一,? 實際上read()方法會將接收到的char類型的數據存儲在Int類型的較低位2個字節中.
如下圖, 因為java中的用的是萬國碼表示字符, 所以1個char字符占2個字節, 而int類型是有4字節的.
注意, 因為網絡, 設備等外部原因可能會導致讀取失敗, 這個read()方法throws IOException, 使用時要捕捉.
3.3 int read(char[] charbuffer) throws IOException
是不是覺得上面的方法一次讀1個字符是不是很蛋疼,? 覺得有點浪費內存的嫌疑啊.
所以Reader 流提供了另1個方法, 可以1次個讀取若干個字符.
這個read方法的名稱和返回值都跟上面的方法相同, 可以講是上面方法的重載.? 但是意義大大不同.
上面的int read()方法 返回值就是接受的字符數據, 只不過用int變量來存儲.
而 這個 int read(char[] charbuffer) 一定要有1個字符數組作為參數, 然后把接受到若干個字符放進這個字符數組(從數組頭部開始放)中. 然后返回實際接受到字符的個數.? 假如讀取到外部設備的結尾, 則返回-1.
例如執行一次下面的語句.
那么程序就會從ReaderA這個流中讀取一次若干(Len)個字符放入到字符數組cbuffer中, len就是具體讀取的字符數據個數.
3.3.1 究竟int read(char[])方法讀取的字符個數是多少個,由什么決定?
通常這個是第一次接觸流的程序猿最想知道的問題.
答案也很簡單.
通常來講,? 這個len就是參數字符串的長度. 也就是說一般來講int read(char[])方法會填滿這個字符串.
但是也有例外:
1. 內存緊張
2. 讀取到最后的部分, 例如外部設備中有89個字符,? 參數字符串的長度是20, 那么前面4次, 都是讀20個字符, 最后那次就只讀9個字符.
3. 讀到外部設備的結尾, 返回-1.
3.3.2 返回值len的意義
有人也會問, 我為什么要關心具體返回值的個數, 這個返回值很重要嗎.
答案是很重要.
原因是: int read(char[]) 執行一次新的讀取時, 并不會擦除參數字符數組的原有數據, 而讀取的字符數不是確定的(上面解析了3個原因), 所以我們需要返回值 len 來確定,數組內那些字符是有效的, 那些字符是之前讀取的.
所以, 我們需要返回值len來在參數字符串中提取有效數據!
下面就是例子:
3.3.3 FileReader的一個例子.
這個例子跟上一篇介紹流的例子都是讀取1個文件并輸出到屏幕上.
代碼:
import java.io.*; import java.util.ArrayList; import java.lang.Integer;public class Reader2{public static void f() throws IOException{FileReader fr = new FileReader("/home/gateman/tmp/build.xml");char[] cbuffer = new char[20]; //not charint len; //throws IOEXCEPTIONArrayList<Integer> lenarr = new ArrayList<Integer>();do{len = fr.read(cbuffer); //if ch = -1, means got the end of the filelenarr.add(len);charArrPrint(cbuffer,len);}while(len > -1); //if ch = -1, means got the end of the fileSystem.out.println("==============================");System.out.println(lenarr);}public static void charArrPrint(char[] cbuffer, int len){int i = 0;for (i=0; i < len; i++){System.out.printf("%c",cbuffer[i]);}} }上面定義了1個長度為20的字符數組cbuffer,然后利用它不斷接受流的數據并輸出到屏幕上, 并記錄每一次的獲取個數數據.
而且打印數組的方法 charArrPrint, 打印的長度是參數len, 而不是數組本身的長度.length.
輸出:
[java] <?xml version="1.0" encoding="GB2312" ?>[java] [java] <!-- a project, maybe includes many groups of tasks(targets) -->[java] <project default="main" basedir=".">[java] [java] <!-- one of the tasks(target) -->[java] <target name="main">[java] [java] <!-- compile -->[java] <javac srcdir="src\main" destdir="build\classes" debug="on" debuglevel="lines,vars,source"/> [java] [java] [java] <!-- run -->[java] <java classname="Enter_1">[java] <classpath>[java] <pathelement path="build\classes"/>[java] <pathelement path="/home/gateman/Studies/Java/java_start/Java_1/jar/generated/Test_jar.jar"/>[java] </classpath>[java] </java>[java] [java] </target>[java] [java] </project>[java] ==============================[java] [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 9, -1]可以見到最終輸出, 實際上基本上所有的讀取次數都是20, 知道最后的9個, 然后再嘗試讀取就返回-1了!
總之,int read(char[]) 方法不能確定每次讀取的字符串的個數, 但是能限制每次讀取的最大個數, 就是傳入參數字符數組的長度.
3.4 int read(char[] charbuffer, int offset, int length) throws IOException
這個方法也是3.1 方法的另1個重載, 參數更加多了.? 其實也不難理解.
1. 參數charbuffer, 也是用于存放接收到的字符數據.
2. 關于第2個參數,但是從第offset個位置開始存放接收到的數據. 也就是說在那一次讀取方法中, 該數組中前offset的數據很可能是以前的數據.
3. 每次接受的個數不能大于第三個參數length, 也就是執行1次read, 最多讀取length,? 當然, 也不能大于數組參數 charbuffer的長度,? 所以 length設置大于charbuffer的長度是無意義的.
4. 也是返回實際接受到的字符個數.
例子: 將上面的例子稍稍修改, 使用當前介紹的函數.
上面例子中我定義了1個長度為20的字符數組, 但是每次最多讀取13個. 而且從字符數組的第5個位置開始存儲.
注意charArrprint()這個函數必須有3個參數了.
輸出:
[java] <?xml version="1.0" encoding="GB2312" ?>[java] [java] <!-- a project, maybe includes many groups of tasks(targets) -->[java] <project default="main" basedir=".">[java] [java] <!-- one of the tasks(target) -->[java] <target name="main">[java] [java] <!-- compile -->[java] <javac srcdir="src\main" destdir="build\classes" debug="on" debuglevel="lines,vars,source"/> [java] [java] [java] <!-- run -->[java] <java classname="Enter_1">[java] <classpath>[java] <pathelement path="build\classes"/>[java] <pathelement path="/home/gateman/Studies/Java/java_start/Java_1/jar/generated/Test_jar.jar"/>[java] </classpath>[java] </java>[java] [java] </target>[java] [java] </project>[java] ==============================[java] [13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, -1]可以見到基本上每次讀取13個, 值到最后一次讀取10個..
3.5 long skip(long n) throws IOException
嘗試跳過n個字符不讀, 返回實際跳過的字符數, 比較少用啦.
3.6 void close() throws IOException
由上面例子中, 我們會覺得流的使用挺簡單的, 只需簡單一兩行就建立1個流.這樣的確很方便了程序猿, 但是可以Reader這個類在后臺做了很多東西, 包括分配內存, 建立連接等, 占用相當的系統資源.
所以我們應該在每次使用完1個流后, 把它關閉掉, 釋放出系統資源!
將上面的例子修改如下, 才是標準的流使用方法:
import java.io.*; import java.util.ArrayList; import java.lang.Integer;public class Reader4{public static void f(){int startIdx = 4;int rLength = 14;FileReader fr = null; // = null is very important, otherwise it will not pass the compilation, popup the error "fr may not be initailed"char[] cbuffer = new char[20]; //not charint len; //throws IOEXCEPTIONArrayList<Integer> lenarr = new ArrayList<Integer>();try{fr = new FileReader("/home/gateman/tmp/build.xml");do{len = fr.read(cbuffer, startIdx, rLength); //if ch = -1, means got the end of the filelenarr.add(len);charArrPrint(cbuffer,startIdx,len);}while(len > -1); //if ch = -1, means got the end of the file}catch(FileNotFoundException e){System.out.println("File not found!");}catch(IOException e){System.out.println("IO Problem!");e.printStackTrace();}catch(Exception e){e.printStackTrace();}finally{if (null != fr){try{fr.close();}catch(IOException e){System.out.println("failed to close the reader!");}} }System.out.println("==============================");System.out.println(lenarr);}public static void charArrPrint(char[] cbuffer, int startIdx, int len){int i ;for (i=0; i < len; i++){System.out.printf("%c",cbuffer[i + startIdx]);}} }值得注意的時, fr.close()方法是放在finally字句內的, 也就是構造函數new FileReader(String filestr) 所在的try{}字句內.
所以第9行的 = null 不能省.
否則編譯失敗, 提示fr有可能未初始化!
四, Writer流及其常用方法.
相對于上面的Reader流, Writer流也是字符流, 但是方向是從程序到外部文本文件.4.1 new Writer()?
構造方法與上面的Reader類似, 有可能throws Exception, 不多說了.
4.2 void write(int c) throws IOException
將1個字符通過輸出流寫到輸出流( 未到達為外部設備)中. 值得注意的是, 傳入的參數字符(char)數據是存放到1個整形(int)變量中. 而且是放在整形變量的低位2個字節中(實際上在jdk1.7中提供了以char類型作為參數的重載方法).
上面紅色加粗的字體意思是: 當執行了這個方法, 字符數據并沒有立即寫入到外部設備, 而是保存在輸出流的緩沖區中(還在內存中).
例子:
import java.io.*;public class Writer1{public static void f(){int i;String s = new String("Just a testing for writer stream!\n");File fl = new File("/home/gateman/tmp/testwriter1.txt");if (fl.exists()){fl.delete();}try{fl.createNewFile();}catch(IOException e){System.out.println("File created failed!");}System.out.println("File created!");FileWriter fw = null;try{fw = new FileWriter(fl); }catch(IOException e){System.out.println("Create filewriter failed!");if (null != fw){try{fw.close();}catch(IOException e1){System.out.println("Close the filewriter failed!");return;}System.out.println("Close the filewriter successfully!");}return;} for (i=0; i<s.length(); i++){try{fw.write((int)s.charAt(i));}catch(IOException e){System.out.println("error occurs in fw.write()!");}}System.out.println("done!");} }上面代碼建立了1個文件/home/gateman/tmp/testwriter1.txt.
然后嘗試使用wrtier 方法把String "Just a testing for writer stream!\n" 的內容寫入到那個文件.
程序輸出:
[java] File created![java] done!BUILD SUCCESSFUL Total time: 1 second gateman@TPEOS Java_1 $ cat /home/gateman/tmp/testwriter1.txt gateman@TPEOS Java_1 $可以見到實際場程序能正常執行完最后一行代碼, 文件也創建成功了, 但是文件是空的.? 因為輸出流在緩沖區的數據并未寫入到文件.
那么如何把輸出流緩沖區的數據寫入到外部設備.
答案很簡單, 執行 流的關閉close()方法即可. 這也是使用流的良好習慣之一.
加上close()的代碼如下:
import java.io.*;public class Writer1{public static void f(){int i;String s = new String("Just a testing for writer stream!\n");File fl = new File("/home/gateman/tmp/testwriter1.txt");if (fl.exists()){fl.delete();}try{fl.createNewFile();}catch(IOException e){System.out.println("File created failed!");}System.out.println("File created!");FileWriter fw = null;try{fw = new FileWriter(fl); }catch(IOException e){System.out.println("Create filewriter failed!");if (null != fw){try{fw.close();}catch(IOException e1){System.out.println("Close the filewriter failed!");return;}System.out.println("Close the filewriter successfully!");}return;} for (i=0; i<s.length(); i++){try{fw.write((int)s.charAt(i));}catch(IOException e){System.out.println("error occurs in fw.write()!");}}try{fw.close();}catch(IOException e){System.out.println("Close the filewriter failed!");return;}System.out.println("Close the filewriter successfully!");System.out.println("done!");} }這次的輸出: [java] File created![java] Close the filewriter successfully![java] done!BUILD SUCCESSFUL Total time: 1 second gateman@TPEOS Java_1 $ cat /home/gateman/tmp/testwriter1.txt Just a testing for writer stream! gateman@TPEOS Java_1 $
提示了關閉文件成功, 而且對應的硬盤上的文件也見到我們寫入的字符數據!
4.3 void flush() throws IOException
根據上面的例子會知道, write()方法寫入的數據未必回立即寫入到外部設備, 而是有1個緩沖區存儲著它們.當close()方法成功時, 才會保證所有緩沖區的內容都寫入到了外部設備.
但是close()方法一般放在1個事務的最后部分執行, 那么中間出現了異常或錯誤導致程序跳出時, 就很可能回丟失緩沖區的數據了.
那么有無1個類似與保存的功能, 讓程序猿在這個期間強制把緩沖區的數據寫入到外部設備中?
答案就是有的,? 這個flush()方法的作用就是把當前緩沖區所有的數據寫入到外部設備.
所以flush() 有時也被稱為"刷新緩沖區", "清空緩沖區"等等, 實際上的意思都是一樣的.
下面這個例子, 即使最后忘記了寫close()方法(盡量避免), 但是執行了flush(), 也保證了數據寫入到了磁盤上的文件:
import java.io.*;public class Writer2{public static void f(){int i;String s = new String("Just a testing for writer stream!\n");File fl = new File("/home/gateman/tmp/testwriter1.txt");if (fl.exists()){fl.delete();}try{fl.createNewFile();}catch(IOException e){System.out.println("File created failed!");}System.out.println("File created!");FileWriter fw = null;try{fw = new FileWriter(fl); }catch(IOException e){System.out.println("Create filewriter fail!");return;} for (i=0; i<s.length(); i++){try{fw.write(s.charAt(i));}catch(IOException e){System.out.println("error occurs in fw.write()!");}}try{fw.flush();}catch(IOException e){System.out.println("error occurs in fw.flush()");}// try{// fw.close();// }catch(IOException e){// System.out.println("Close the filewriter failed!");// return;// }// System.out.println("Close the filewriter successfully!");System.out.println("done!");} }輸出: [java] File created![java] done!BUILD SUCCESSFUL Total time: 1 second gateman@TPEOS Java_1 $ cat /home/gateman/tmp/testwriter1.txt Just a testing for writer stream!
4.4 void write(char[] cbuffer) throws IOException
此方法是4.2方法的重載.可以把1個字符數組的所有數據寫入到輸出流緩沖區..
4.5 void write(String s) throws IOException
這個方法也是上面4.2 的重載方法,? 很容易理解啊, 就是把整個字符串的內容寫入到輸出流緩沖區. 這里不寫例子代碼了.
對比Reader流.
為什么輸出流可以把一個字符串or字符數組寫入到輸出流
而Reader流用于接受一段字符數據必須用字符數組?
這時因為在java中,字符串實際上是常量, 在靜態區不支持修改. 這需要深入理解java的字符串.
4.6 void write(String s, int offset, int length) throws IOException
也很簡單啦, 就是把字符串的一部分寫入輸出流緩沖區..
五, InputStream及其常用方法.
所謂InputStream就是字節輸入流, 與Reader最大的區別就是它不但支持文本外部設備,還支持2進制外部設備.
這里用的例子是FileInputStream, 顧名思義, 就是搭向磁盤文件的字節輸入流.
5.1 new 方法
構造方法與Reader類似, 不再詳講了, 參數可以是1個File對象, 也可以是1個String(文件路徑), Throws 異常!
5.2 int read() throws IOException
讀取1個字節(2進制),并以整數形式(10進制)返回, 如果讀到流的末尾,則返回-1.1個簡單例子, 與FileReader的使用方法很類似.
輸出: [java] <?xml version="1.0" encoding="GB2312" ?>[java] [java] <!-- a project, maybe includes many groups of tasks(targets) -->[java] <project default="main" basedir=".">[java] [java] <!-- one of the tasks(target) -->[java] <target name="main">[java] [java] <!-- compile -->[java] <javac srcdir="src\main" destdir="build\classes" debug="on" debuglevel="lines,vars,source"/> [java] [java] [java] <!-- run -->[java] <java classname="Enter_1">[java] <classpath>[java] <pathelement path="build\classes"/>[java] <pathelement path="/home/gateman/Studies/Java/java_start/Java_1/jar/generated/Test_jar.jar"/>[java] </classpath>[java] </java>[java] [java] </target>[java] [java] </project>[java] ============[java] Stream close successfully!BUILD SUCCESSFUL Total time: 1 second
5.3 int available() throws IOException
先看看這個方法在jdk api的字面解釋
返回下一次對此輸入流調用的方法可以不受阻塞地從此輸入流讀取(或跳過)的估計剩余字節數。下一次調用可能是同一個線程,也可能是另一個線程。一次讀取或跳過此數量個字節不會發生阻塞,但讀取或跳過的字節可能小于該數。
在某些情況下,非阻塞的讀取(或跳過)操作在執行很慢時看起來受阻塞,例如,在網速緩慢的網絡上讀取大文件時。
簡答地將, 這個方法返回輸入流中還有多少剩余字節數.
如果是在連接磁盤文件的輸入流中, 一般就是返回磁盤文件的(剩余)大小.
但是這個方法在網絡流中更有意義
例如網絡上1個流連接兩個程序傳輸, 1個程序往往要等待另1個程序向流發出數據, 那么這個方法就可以用于判斷流中是否存在數據了!
5.4 int read(byte[] b) throws IOException
5.2方法的重載, 讀取一定數量的字字皆, 并存儲在字節數組b中, 返回實際讀取的字節數. 如果讀到輸出流的末尾, 則返回-1.
這個方法的用法很想 Reader流的 int read(char[] cbuffer) .
5.5 int read(byte[] b, int offset, int length) throws IOException
上面的方法的重載,讀取一定量的字節, 存放在數組b的特定位置...
六, OutputStream及其常用方法.
這個本文介紹的最后1個基本流, 輸出字節流..? 其用法與Writer流很類似.. 不再詳細寫例子介紹了..
6.1 void write(int b) throws IOException
向輸出流緩沖區中寫入1個byte的數據, 該字節數據為參數整型b的低8位.6.2 void write(byte[] b) throws IOException
將1個字節數組b的內容寫入輸入流緩沖區.
6.3 void write(byte[] b, int offset, int length) throws IOException
將字節數組b的部分內容寫入輸入流緩沖區
6.4 void flush() throws IOException
立即將緩沖區的數據寫入到外部設備
總結
以上是生活随笔為你收集整理的Java Stream(流)的分类, 四大基本流的介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Stream简介, 流的基本概
- 下一篇: Java 缓冲流简介及简单用法