Java流類圖結構:


流的概念和作用
流是一組有順序的,有起點和終點的字節集合,是對數據傳輸的總稱或抽象。即數據在兩設備間的傳輸稱為流,流的本質是數據傳輸,根據數據傳輸特性將流抽象為各種類,方便更直觀的進行數據操作。
IO流的分類
- 根據處理數據類型的不同分為:字符流和字節流
- 根據數據流向不同分為:輸入流和輸出流
字符流和字節流
字符流的由來: 因為數據編碼的不同,而有了對字符進行高效操作的流對象。本質其實就是基於字節流讀取時,去查了指定的碼表。 字節流和字符流的區別:
- 讀寫單位不同:字節流以字節(8bit)為單位,字符流以字符為單位,根據碼表映射字符,一次可能讀多個字節。
-
處理對象不同:字節流能處理所有類型的數據(如圖片、avi等),而字符流只能處理字符類型的數據。
- 字節流:一次讀入或讀出是8位二進制。
-
字符流:一次讀入或讀出是16位二進制。
設備上的數據無論是圖片或者視頻,文字,它們都以二進制存儲的。二進制的最終都是以一個8位為數據單元進行體現,所以計算機中的最小數據單元就是字節。意味着,字節流可以處理設備上的所有數據,所以字節流一樣可以處理字符數據。
結論:只要是處理純文本數據,就優先考慮使用字符流。 除此之外都使用字節流。
輸入流和輸出流
輸入流只能進行讀操作,輸出流只能進行寫操作,程序中需要根據待傳輸數據的不同特性而使用不同的流。
輸入字節流 InputStream

我們看到的具體的某一些管道,凡是以InputStream結尾的管道,都是以字節的形式向我們的程序輸入數據。
- InputStream 是所有的輸入字節流的父類,它是一個抽象類。
- ByteArrayInputStream 、 StringBufferInputStream 、 FileInputStream 是三種基本的介質流,它們分別從 Byte 數組 、 StringBuffer 、和 本地文件 中讀取數據。
- PipedInputStream 是從與其它線程共用的管道中讀取數據,與Piped 相關的知識后續單獨介紹。
- ObjectInputStream 和所有 FilterInputStream 的子類都是裝飾流(裝飾器模式的主角)。
InputStream的基本方法

read()方法是一個字節一個字節地往外讀,每讀取一個字節,就處理一個字節。read(byte[] buffer)方法讀取數據時,先把讀取到的數據填滿這個byte[]類型的數組buffer(buffer是內存里面的一塊緩沖區),然后再處理數組里面的數據。這就跟我們取水一樣,先用一個桶去接,等桶接滿水后再處理桶里面的水。如果是每讀取一個字節就處理一個字節,這樣子讀取也太累了。
輸出字節流 OutputStream

- OutputStream 是所有的輸出字節流的父類,它是一個抽象類。
- ByteArrayOutputStream 、 FileOutputStream 是兩種基本的介質流,它們分別向 Byte 數組 、和 本地文件 中寫入數據。
- PipedOutputStream 是向與其它線程共用的管道中寫入數據。
- ObjectOutputStream 和所有 FilterOutputStream 的子類都是裝飾流。
OutputStream的基本方法

總結:
- 輸入流:InputStream或者Reader:從文件中讀到程序中;
- 輸出流:OutputStream或者Writer:從程序中輸出到文件中;
Reader流

Reader的基本方法

Writer流

Writer的基本方法

節點流
節點流:直接與數據源相連,讀入或讀出。
直接使用節點流,讀寫不方便,為了更快的讀寫文件,才有了處理流。
常用的節點流


- 父 類 : InputStream 、 OutputStream 、 Reader 、 Writer
- 文 件 : FileInputStream 、 FileOutputStrean 、 FileReader 、 FileWriter 文件進行處理的節點流
- 數 組 : ByteArrayInputStream 、 ByteArrayOutputStream 、 CharArrayReader 、 CharArrayWriter 對數組進行處理的節點流(對應的不再是文件,而是內存中的一個數組)
- 字符串 : StringReader 、 StringWriter 對字符串進行處理的節點流
- 管 道 : PipedInputStream 、 PipedOutputStream 、 PipedReader 、 PipedWriter 對管道進行處理的節點流
節點流就是一根管道直接插到數據源上面,直接讀數據源里面的數據,或者是直接往數據源里面寫入數據。典型的節點流是文件流:文件的字節輸入流(FileInputStream),文件的字節輸出流(FileOutputStream),文件的字符輸入流(FileReader),文件的字符輸出流(FileWriter)。
處理流
處理流和節點流一塊使用,在節點流的基礎上,再套接一層,套接在節點流上的就是處理流。如 BufferedReader .處理流的構造方法總是要帶一個其他的流對象做參數。一個流對象經過其他流的多次包裝,稱為流的鏈接。
你要是對原始的流不滿意,你可以在這根管道外面再套其它的管道,套在其它管道之上的流叫處理流。為什么需要處理流呢?這就跟水流里面有雜質,你要過濾它,你可以再套一層管道過濾這些雜質一樣。
常用的處理流

- 緩沖流: BufferedInputStream 、 BufferedOutputStream 、 BufferedReader 、 BufferedWriter 增加緩沖功能,避免頻繁讀寫硬盤。
- 轉換流: InputStreamReader 、 OutputStreamReader 實現字節流和字符流之間的轉換。
- 數據流: DataInputStream 、 DataOutputStream 等-提供將基礎數據類型寫入到文件中,或者讀取出來。
轉換流
InputStreamReader 、 OutputStreamWriter 要 InputStream 或 OutputStream 作為參數,實現從字節流到字符流的轉換。
構造函數
InputStreamReader(InputStream); //通過構造函數初始化,使用的是本系統默認的編碼表GBK。 InputStreamWriter(InputStream,String charSet); //通過該構造函數初始化,可以指定編碼表。 OutputStreamWriter(OutputStream); //通過該構造函數初始化,使用的是本系統默認的編碼表GBK。 OutputStreamwriter(OutputStream,String charSet); //通過該構造函數初始化,可以指定編碼表。
實戰演練
FileInputStream類的使用:讀取文件內容
package com.app; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class A1 { public static void main(String[] args) { A1 a1 = new A1(); //電腦d盤中的abc.txt 文檔 String filePath = "D:/abc.txt" ; String reslut = a1.readFile( filePath ) ; System.out.println( reslut ); } /** * 讀取指定文件的內容 * @param filePath : 文件的路徑 * @return 返回的結果 */ public String readFile( String filePath ){ FileInputStream fis=null; String result = "" ; try { // 根據path路徑實例化一個輸入流的對象 fis = new FileInputStream( filePath ); //2. 返回這個輸入流中可以被讀的剩下的bytes字節的估計值; int size = fis.available() ; //3. 根據輸入流中的字節數創建byte數組; byte[] array = new byte[size]; //4.把數據讀取到數組中; fis.read( array ) ; //5.根據獲取到的Byte數組新建一個字符串,然后輸出; result = new String(array); } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); }finally{ if ( fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } return result ; } }
package cn.galc.test; import java.io.*; public class TestFileInputStream { public static void main(String args[]) { int b = 0;// 使用變量b來裝調用read()方法時返回的整數 FileInputStream in = null; // 使用FileInputStream流來讀取有中文的內容時,讀出來的是亂碼,因為使用InputStream流里面的read()方法讀取內容時是一個字節一個字節地讀取的,而一個漢字是占用兩個字節的,所以讀取出來的漢字無法正確顯示。 // FileReader in = null;//使用FileReader流來讀取內容時,中英文都可以正確顯示,因為Reader流里面的read()方法是一個字符一個字符地讀取的,這樣每次讀取出來的都是一個完整的漢字,這樣就可以正確顯示了。 try { in = new FileInputStream("D:\\Java\\MyEclipse 10\\Workspaces\\AnnotationTest\\src\\cn\\galc\\test\\FileInputStream.java"); // in = new FileReader("D:/java/io/TestFileInputStream.java"); } catch (FileNotFoundException e) { System.out.println("系統找不到指定文件!"); System.exit(-1);// 系統非正常退出 } long num = 0;// 使用變量num來記錄讀取到的字符數 try {// 調用read()方法時會拋異常,所以需要捕獲異常 while ((b = in.read()) != -1) { // 調用int read() throws Exception方法時,返回的是一個int類型的整數 // 循環結束的條件就是返回一個值-1,表示此時已經讀取到文件的末尾了。 // System.out.print(b+"\t");//如果沒有使用“(char)b”進行轉換,那么直接打印出來的b就是數字,而不是英文和中文了 System.out.print((char) b); // “char(b)”把使用數字表示的漢字和英文字母轉換成字符輸入 num++; } in.close();// 關閉輸入流 System.out.println(); System.out.println("總共讀取了" + num + "個字節的文件"); } catch (IOException e1) { System.out.println("文件讀取錯誤!"); } } }
FileInputStream和FileOutputStream這兩個流都是字節流,都是以一個字節為單位進行輸入和輸出的。所以對於占用2個字節存儲空間的字符來說讀取出來時就會顯示成亂碼。
FileOutputStream 類的使用:將內容寫入文件
package com.app; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class A2 { public static void main(String[] args) { A2 a2 = new A2(); //電腦d盤中的abc.txt 文檔 String filePath = "D:/abc.txt" ; //要寫入的內容 String content = "今天是2017/1/9,天氣很好" ; a2.writeFile( filePath , content ) ; } /** * 根據文件路徑創建輸出流 * @param filePath : 文件的路徑 * @param content : 需要寫入的內容 */ public void writeFile( String filePath , String content ){ FileOutputStream fos = null ; try { //1、根據文件路徑創建輸出流 fos = new FileOutputStream( filePath ); //2、把string轉換為byte數組; byte[] array = content.getBytes() ; //3、把byte數組輸出; fos.write( array ); } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); }finally{ if ( fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
注意:
- 在實際的項目中,所有的IO操作都應該放到子線程中操作,避免堵住主線程。
- FileInputStream 在讀取文件內容的時候,我們傳入文件的路徑( "D:/abc.txt" ), 如果這個路徑下的文件不存在,那么在執行 readFile() 方法時會報 FileNotFoundException 異常。
- FileOutputStream 在寫入文件的時候,我們傳入文件的路徑( "D:/abc.txt" ), 如果這個路徑下的文件不存在,那么在執行 writeFile() 方法時, 會默認給我們創建一個新的文件。還有重要的一點,不會報異常。
效果圖:

范例:使用FileWriter(字符流)向指定文件中寫入數據
package cn.galc.test; /*使用FileWriter(字符流)向指定文件中寫入數據 寫入數據時以1個字符為單位進行寫入*/ import java.io.*; public class TestFileWriter{ public static void main(String args[]){ /*使用FileWriter輸出流從程序把數據寫入到Uicode.dat文件中 使用FileWriter流向文件寫入數據時是一個字符一個字符寫入的*/ FileWriter fw = null; try{ fw = new FileWriter("D:/java/Uicode.dat"); //字符的本質是一個無符號的16位整數 //字符在計算機內部占用2個字節 //這里使用for循環把0~60000里面的所有整數都輸出 //這里相當於是把全世界各個國家的文字都0~60000內的整數的形式來表示 for(int c=0;c<=60000;c++){ fw.write(c); //使用write(int c)把0~60000內的整數寫入到指定文件內 //調用write()方法時,我認為在執行的過程中應該使用了“(char)c”進行強制轉換,即把整數轉換成字符來顯示 //因為打開寫入數據的文件可以看到,里面顯示的數據並不是0~60000內的整數,而是不同國家的文字的表示方式 } /*使用FileReader(字符流)讀取指定文件里面的內容 讀取內容時是以一個字符為單位進行讀取的*/ int b = 0; long num = 0; FileReader fr = null; fr = new FileReader("D:/java/Uicode.dat"); while((b = fr.read())!= -1){ System.out.print((char)b + "\t"); num++; } System.out.println(); System.out.println("總共讀取了"+num+"個字符"); }catch(Exception e){ e.printStackTrace(); } } }
FileReader和FileWriter這兩個流都是字符流,都是以一個字符為單位進行輸入和輸出的。所以讀取和寫入占用2個字節的字符時都可以正常地顯示出來,以上是以File(文件)這個類型為例對節點流進行了講解,所謂的節點流指定就是直接把輸入流或輸出插入到數據源上,直接往數據源里面寫入數據或讀取數據。
綜合練習,實現復制文件,從D盤復制到E盤
package com.app; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class A3 { public static void main(String[] args) { A3 a2 = new A3(); //電腦d盤中的cat.png 圖片的路徑 String filePath1 = "D:/cat.png" ; //電腦e盤中的cat.png 圖片的路徑 String filePath2 = "E:/cat.png" ; //復制文件 a2.copyFile( filePath1 , filePath2 ); } /** * 文件復制 * @param filePath_old : 需要復制文件的路徑 * @param filePath_new : 復制文件存放的路徑 */ public void copyFile( String filePath_old , String filePath_new){ FileInputStream fis=null ; FileOutputStream fout = null ; try { // 根據path路徑實例化一個輸入流的對象 fis = new FileInputStream( filePath_old ); //2. 返回這個輸入流中可以被讀的剩下的bytes字節的估計值; int size = fis.available() ; //3. 根據輸入流中的字節數創建byte數組; byte[] array = new byte[size]; //4.把數據讀取到數組中; fis.read( array ) ; //5、根據文件路徑創建輸出流 fout = new FileOutputStream( filePath_new ) ; //5、把byte數組輸出; fout.write( array ); } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); }finally{ if ( fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if ( fout != null ) { try { fout.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
