io

一、JAVA流式輸入/輸出原理

  

  流是用來讀寫數據的,java有一個類叫File,它封裝的是文件的文件名,只是內存裏面的一個對象,真正的文件是在硬盤上的一塊空間,在這個文件裏面存放着各種各樣的數據,我們想讀文件裏面的數據怎麼辦呢?是通過一個流的方式來讀,咱們要想從程序讀數據,對於計算機來說,無論讀什麼類型的數據都是以010101101010這樣的形式讀取的。怎麼把文件裏面的數據讀出來呢?你可以把文件想象成一個小桶,文件就是一個桶,文件裏面的數據就相當於是這個桶裏面的水,那麼我們怎麼從這個桶裏面取水呢,也就是怎麼從這個文件讀取數據呢。

  常見的取水的辦法是我們用一根管道插到桶上面,然後在管道的另一邊打開水龍頭,桶裏面的水就開始嘩啦嘩啦地從水龍頭裏流出來了,桶裏面的水是通過這根管道流出來的,因此這根管道就叫流,JAVA裏面的流式輸入/輸出跟水流的原理一模一樣,當你要從文件讀取數據的時候,一根管道插到文件裏面去,然後文件裏面的數據就順着管道流出來,這時你在管道的另一頭就可以讀取到從文件流出來的各種各樣的數據了。當你要往文件寫入數據時,也是通過一根管道,讓要寫入的數據通過這根管道嘩啦嘩啦地流進文件裏面去。除了從文件去取數據以外,還可以通過網絡,比如用一根管道把我和你的機子連接起來,我說一句話,通過這個管道流進你的機子裏面,你馬上就可以看得到,而你說一句話,通過這根管道流到我的機子裏面,我也馬上就可以看到。有的時候,一根管道不夠用,比方說這根管道流過來的水有一些雜質,我們就可以在這個根管道的外面再包一層管道,把雜質給過濾掉。從程序的角度來講,從計算機讀取到的原始數據肯定都是010101這種形式的,一個字節一個字節地往外讀,當你這樣讀的時候你覺得這樣的方法不合適,沒關係,你再在這根管道的外面再包一層比較強大的管道,這個管道可以把010101幫你轉換成字符串。這樣你使用程序讀取數據時讀到的就不再是010101這種形式的數據了,而是一些可以看得懂的字符串了。

 二、輸入輸出流分類

  io包裏面定義了所有的流,所以一說流指的就是io包裏面的

  什麼叫輸入流?什麼叫輸出流?用一根管道一端插進文件里程序裏面,然後開始讀數據,那麼這是輸入還是輸出呢?如果站在文件的角度上,這叫輸出,如果站在程序的角度上,這叫輸入。

  記住,以後說輸入流和輸出流都是站在程序的角度上來說。

三、節點流和處理流

  

   你要是對原始的流不滿意,你可以在這根管道外面再套其它的管道,套在其它管道之上的流叫處理流。爲什麼需要處理流呢?這就跟水流裏面有雜質,你要過濾它,你可以再套一層管道過濾這些雜質一樣。

 3.1.節點流類型

  

  節點流就是一根管道直接插到數據源上面,直接讀數據源裏面的數據,或者是直接往數據源裏面寫入數據。典型的節點流是文件流:文件的字節輸入流(FileInputStream),文件的字節輸出流(FileOutputStream),文件的字符輸入流(FileReader),文件的字符輸出流(FileWriter)。

3.2.處理流類型

  

  處理流是包在別的流上面的流,相當於是包到別的管道上面的管道。

 四、InputStream(輸入流)

  

  我們看到的具體的某一些管道,凡是以InputStream結尾的管道,都是以字節的形式向我們的程序輸入數據。

4.1.InputStream的基本方法

  

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

 五、OutputStream(輸出流)

  

5.1.OutputStream的基本方法

  

六、Reader流

  

6.1.Reader的基本方法

  

七、Writer流

  

7.1.Writer的基本方法

  

 八、節點流講解

  以File(文件)這個類型作爲講解節點流的典型代表

  

 範例:使用FileInputStream流來讀取FileInputStream.java文件的內容

 1 package cn.galc.test;
 2 
 3 import java.io.*;
 4 
 5 public class TestFileInputStream {
 6     public static void main(String args[]) {
 7         int b = 0;// 使用變量b來裝調用read()方法時返回的整數
 8         FileInputStream in = null;
 9         // 使用FileInputStream流來讀取有中文的內容時,讀出來的是亂碼,因爲使用InputStream流裏面的read()方法讀取內容時是一個字節一個字節地讀取的,而一個漢字是佔用兩個字節的,所以讀取出來的漢字無法正確顯示。
10         // FileReader in = null;//使用FileReader流來讀取內容時,中英文都可以正確顯示,因爲Reader流裏面的read()方法是一個字符一個字符地讀取的,這樣每次讀取出來的都是一個完整的漢字,這樣就可以正確顯示了。
11         try {
12             in = new FileInputStream("D:\\Java\\MyEclipse 10\\Workspaces\\AnnotationTest\\src\\cn\\galc\\test\\FileInputStream.java");
13             // in = new FileReader("D:/java/io/TestFileInputStream.java");
14         } catch (FileNotFoundException e) {
15             System.out.println("系統找不到指定文件!");
16             System.exit(-1);// 系統非正常退出
17         }
18         long num = 0;// 使用變量num來記錄讀取到的字符數
19         try {// 調用read()方法時會拋異常,所以需要捕獲異常
20             while ((b = in.read()) != -1) {
21                 // 調用int read() throws Exception方法時,返回的是一個int類型的整數
22                 // 循環結束的條件就是返回一個值-1,表示此時已經讀取到文件的末尾了。
23                 // System.out.print(b+"\t");//如果沒有使用“(char)b”進行轉換,那麼直接打印出來的b就是數字,而不是英文和中文了
24                 System.out.print((char) b);
25                 // “char(b)”把使用數字表示的漢字和英文字母轉換成字符輸入
26                 num++;
27             }
28             in.close();// 關閉輸入流
29             System.out.println();
30             System.out.println("總共讀取了" + num + "個字節的文件");
31         } catch (IOException e1) {
32             System.out.println("文件讀取錯誤!");
33         }
34     }
35 }

範例:使用FileOutputStream流往一個文件裏面寫入數據

 1 package cn.galc.test;
 2 
 3 import java.io.*;
 4 
 5 public class TestFileOutputStream {
 6     public static void main(String args[]) {
 7         int b = 0;
 8         FileInputStream in = null;
 9         FileOutputStream out = null;
10         try {
11             in = new FileInputStream("D:\\Java\\MyEclipse 10\\Workspaces\\AnnotationTest\\src\\cn\\galc\\test\\MyMouseAdapter.java");
12             out = new FileOutputStream("D:/java/TestFileOutputStream1.java");
13             // 指明要寫入數據的文件,如果指定的路徑中不存在TestFileOutputStream1.java這樣的文件,則系統會自動創建一個
14             while ((b = in.read()) != -1) {
15                 out.write(b);
16                 // 調用write(int c)方法把讀取到的字符全部寫入到指定文件中去
17             }
18             in.close();
19             out.close();
20         } catch (FileNotFoundException e) {
21             System.out.println("文件讀取失敗");
22             System.exit(-1);// 非正常退出
23         } catch (IOException e1) {
24             System.out.println("文件複製失敗!");
25             System.exit(-1);
26         }
27         System.out
28                 .println("TestFileInputStream.java文件裏面的內容已經成功複製到文件TestFileOutStream1.java裏面");
29     }
30 }

  FileInputStream和FileOutputStream這兩個流都是字節流,都是以一個字節爲單位進行輸入和輸出的。所以對於佔用2個字節存儲空間的字符來說讀取出來時就會顯示成亂碼。

 範例:使用FileWriter(字符流)向指定文件中寫入數據

 1 package cn.galc.test;
 2 
 3 /*使用FileWriter(字符流)向指定文件中寫入數據
 4 寫入數據時以1個字符爲單位進行寫入*/
 5 import java.io.*;
 6 public class TestFileWriter{
 7     public static void main(String args[]){
 8         /*使用FileWriter輸出流從程序把數據寫入到Uicode.dat文件中
 9         使用FileWriter流向文件寫入數據時是一個字符一個字符寫入的*/
10         FileWriter fw = null;
11         try{
12                 fw = new FileWriter("D:/java/Uicode.dat");
13                 //字符的本質是一個無符號的16位整數
14                 //字符在計算機內部佔用2個字節
15                 //這裏使用for循環把0~60000裏面的所有整數都輸出
16                 //這裏相當於是把全世界各個國家的文字都0~60000內的整數的形式來表示
17                 for(int c=0;c<=60000;c++){
18                     fw.write(c);
19                     //使用write(int c)把0~60000內的整數寫入到指定文件內
20                     //調用write()方法時,我認爲在執行的過程中應該使用了“(char)c”進行強制轉換,即把整數轉換成字符來顯示
21                     //因爲打開寫入數據的文件可以看到,裏面顯示的數據並不是0~60000內的整數,而是不同國家的文字的表示方式
22             }
23             /*使用FileReader(字符流)讀取指定文件裏面的內容
24             讀取內容時是以一個字符爲單位進行讀取的*/
25                 int b = 0;
26                 long num = 0;
27                 FileReader fr = null;
28                 fr = new FileReader("D:/java/Uicode.dat");
29                 while((b = fr.read())!= -1){
30                     System.out.print((char)b + "\t");
31                     num++;
32                 }
33                 System.out.println();
34                 System.out.println("總共讀取了"+num+"個字符");
35         }catch(Exception e){
36             e.printStackTrace();
37         }
38     }
39 }

  FileReaderFileWriter這兩個流都是字符流,都是以一個字符爲單位進行輸入和輸出的。所以讀取和寫入佔用2個字節的字符時都可以正常地顯示出來,以上是以File(文件)這個類型爲例對節點流進行了講解,所謂的節點流指定就是直接把輸入流或輸出插入到數據源上,直接往數據源裏面寫入數據或讀取數據。

九、處理流講解

9.1.第一種處理流——緩衝流(Buffering)

   

  帶有緩衝區的,緩衝區(Buffer)就是內存裏面的一小塊區域,讀寫數據時都是先把數據放到這塊緩衝區域裏面,減少io對硬盤的訪問次數,保護我們的硬盤。可以把緩衝區想象成一個小桶,把要讀寫的數據想象成水,每次讀取數據或者是寫入數據之前,都是先把數據裝到這個桶裏面,裝滿了以後再做處理。這就是所謂的緩衝。先把數據放置到緩衝區上,等到緩衝區滿了以後,再一次把緩衝區裏面的數據寫入到硬盤上或者讀取出來,這樣可以有效地減少對硬盤的訪問次數,有利於保護我們的硬盤。

緩衝流測試代碼:

 1 package cn.gacl.test;
 2 
 3 import java.io.*;
 4 
 5 public class TestBufferStream {
 6     public static void main(String args[]) {
 7         FileInputStream fis = null;
 8         try {
 9             fis = new FileInputStream("D:/java/TestFileInputStream.java");
10             // 在FileInputStream節點流的外面套接一層處理流BufferedInputStream
11             BufferedInputStream bis = new BufferedInputStream(fis);
12             int c = 0;
13             System.out.println((char) bis.read());
14             System.out.println((char) bis.read());
15             bis.mark(100);// 在第100個字符處做一個標記
16             for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
17                 System.out.print((char) c);
18             }
19             System.out.println();
20             bis.reset();// 重新回到原來標記的地方
21             for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
22                 System.out.print((char) c);
23             }
24             bis.close();
25         } catch (FileNotFoundException e) {
26             e.printStackTrace();
27         } catch (Exception e1) {
28             e1.printStackTrace();
29         }
30     }
31 }
 1 package cn.gacl.test;
 2 
 3 import java.io.*;
 4 public class TestBufferStream1{
 5     public static void main(String args[]){
 6         try{
 7         BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\java\\dat.txt"));
 8             //在節點流FileWriter的外面再套一層處理流BufferedWriter
 9             String s = null;
10             for(int i=0;i<100;i++){
11                 s = String.valueOf(Math.random());//“Math.random()”將會生成一系列介於0~1之間的隨機數。
12                 // static String valueOf(double d)這個valueOf()方法的作用就是把一個double類型的數轉換成字符串
13                 //valueOf()是一個靜態方法,所以可以使用“類型.靜態方法名”的形式來調用 
14                 bw.write(s);//把隨機數字符串寫入到指定文件中
15                 bw.newLine();//調用newLine()方法使得每寫入一個隨機數就換行顯示
16             }
17             bw.flush();//調用flush()方法清空緩衝區
18             
19         BufferedReader br = new BufferedReader(new FileReader("D:/java/dat.txt"));
20                 //在節點流FileReader的外面再套一層處理流BufferedReader
21             while((s = br.readLine())!=null){
22                 //使用BufferedReader處理流裏面提供String readLine()方法讀取文件中的數據時是一行一行讀取的
23                 //循環結束的條件就是使用readLine()方法讀取數據返回的字符串爲空值後則表示已經讀取到文件的末尾了。
24                 System.out.println(s);
25                 
26             }
27         bw.close();
28         br.close();
29         }catch(Exception e){
30             e.printStackTrace();
31         }
32     }
33 }

  

  程序的輸入指的是把從文件讀取到的內容存儲到爲程序分配的內存區域裏面去。流,什麼是流,流無非就是兩根管道,一根向裏,一根向外,向裏向外都是對於我們自己寫的程序來說,流分爲各種各樣的類型,不同的分類方式又可以分爲不同的類型,根據方向來分,分爲輸入流和輸出流,根據讀取數據的單位的不同,又可以分爲字符流和字節流,除此之外,還可以分爲節點流和處理流,節點流就是直接和數據源連接的流,處理流就是包在其它流上面的流,處理流不是直接和數據源連接,而是從數據源讀取到數據以後再通過處理流處理一遍。緩衝流也包含了四個類:BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter。流都是成對的,沒有流是是不成對的,肯定是一個in,一個out

9.2.第二種處理流——轉換流

  

  轉換流非常的有用,它可以把一個字節流轉換成一個字符流,轉換流有兩種,一種叫InputStreamReader,另一種叫OutputStreamWriterInputStream是字節流,Reader是字符流,InputStreamReader就是把InputStream轉換成ReaderOutputStream是字節流,Writer是字符流,OutputStreamWriter就是把OutputStream轉換成Writer。把OutputStream轉換成Writer之後就可以一個字符一個字符地通過管道寫入數據了,而且還可以寫入字符串。我們如果用一個FileOutputStream流往文件裏面寫東西,得要一個字節一個字節地寫進去,但是如果我們在FileOutputStream流上面套上一個字符轉換流,那我們就可以一個字符串一個字符串地寫進去。

轉換流測試代碼:

 1 package cn.gacl.test;
 2 
 3 import java.io.*;
 4 
 5 public class TestTransform1 {
 6     public static void main(String args[]) {
 7         try {
 8             OutputStreamWriter osw = new OutputStreamWriter(
 9                     new FileOutputStream("D:/java/char.txt"));
10             osw.write("MircosoftsunIBMOracleApplet");// 把字符串寫入到指定的文件中去
11             System.out.println(osw.getEncoding());// 使用getEncoding()方法取得當前系統的默認字符編碼
12             osw.close();
13             osw = new OutputStreamWriter(new FileOutputStream(
14                     "D:\\java\\char.txt", true), "ISO8859_1");
15             // 如果在調用FileOutputStream的構造方法時沒有加入true,那麼新加入的字符串就會替換掉原來寫入的字符串,在調用構造方法時指定了字符的編碼
16             osw.write("MircosoftsunIBMOracleApplet");// 再次向指定的文件寫入字符串,新寫入的字符串加入到原來字符串的後面
17             System.out.println(osw.getEncoding());
18             osw.close();
19 
20         } catch (Exception e) {
21             e.printStackTrace();
22         }
23     }
24 }

 1 package cn.gacl.test;
 2 
 3 import java.io.*;
 4 public class TestTransform2{
 5     public static void main(String args[]){
 6         try{
 7             InputStreamReader isr = new InputStreamReader(System.in);
 8             //System.in這裏的in是一個標準的輸入流,用來接收從鍵盤輸入的數據
 9             BufferedReader br = new BufferedReader(isr);
10             String s = null;
11             s = br.readLine();//使用readLine()方法把讀取到的一行字符串保存到字符串變量s中去
12             while(s != null){
13                 System.out.println(s.toUpperCase());//把保存在內存s中的字符串打印出來
14                 s = br.readLine();//在循環體內繼續接收從鍵盤的輸入
15                 if(s.equalsIgnoreCase("exit")){
16                     //只要輸入exit循環就結束,就會退出
17                     break;
18                 }
19             }
20         }catch(Exception e){
21             e.printStackTrace();
22         }
23     }
24 }

9.3.第三種處理流——數據流

  

數據流測試代碼:

 1 package cn.gacl.test;
 2 
 3 import java.io.*;
 4 public class TestDataStream{
 5     public static void main(String args[]){
 6         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 7         //在調用構造方法時,首先會在內存裏面創建一個ByteArray字節數組
 8         DataOutputStream dos = new DataOutputStream(baos);
 9         //在輸出流的外面套上一層數據流,用來處理int,double類型的數
10         try{
11             dos.writeDouble(Math.random());//把產生的隨機數直接寫入到字節數組ByteArray中
12             dos.writeBoolean(true);//布爾類型的數據在內存中就只佔一個字節
13         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
14             System.out.println(bais.available());
15             DataInputStream dis = new DataInputStream(bais);
16             System.out.println(dis.readDouble());//先寫進去的就先讀出來,調用readDouble()方法讀取出寫入的隨機數
17             System.out.println(dis.readBoolean());//後寫進去的就後讀出來,這裏面的讀取順序不能更改位置,否則會打印出不正確的結果
18             dos.close();
19             bais.close();
20         }catch(Exception e){
21                 e.printStackTrace();
22             }
23     }
24 }

  通過bais這個流往外讀取數據的時候,是一個字節一個字節地往外讀取的,因此讀出來的數據無法判斷是字符串還是bool類型的值,因此要在它的外面再套一個流,通過dataInputStream把讀出來的數據轉換就可以判斷了。注意了:讀取數據的時候是先寫進去的就先讀出來,因此讀ByteArray字節數組數據的順序應該是先把佔8個字節的double類型的數讀出來,然後再讀那個只佔一個字節的boolean類型的數,因爲double類型的數是先寫進數組裏面的,讀的時候也要先讀它。這就是所謂的先寫的要先讀。如果先讀Boolean類型的那個數,那麼讀出來的情況可能就是把double類型數的8個字節裏面的一個字節讀了出來。 

9.4.打印流——Print

  

測試代碼:

 1 package cn.gacl.test;
 2 
 3 /*這個小程序是重新設置打印輸出的窗口,
 4  * 把默認在命令行窗口輸出打印內容設置成其他指定的打印顯示窗口
 5  */
 6 import java.io.*;
 7 public class TestPrintStream{
 8     public static void main(String args[]){
 9         PrintStream ps = null;
10         try{
11                 FileOutputStream fos = new FileOutputStream("D:/java/log.txt");
12                 ps = new PrintStream(fos);//在輸出流的外面套接一層打印流,用來控制打印輸出
13                 if(ps != null){
14                     System.setOut(ps);//這裏調用setOut()方法改變了輸出窗口,以前寫System.out.print()默認的輸出窗口就是命令行窗口.
15                     //但現在使用System.setOut(ps)將打印輸出窗口改成了由ps指定的文件裏面,通過這樣設置以後,打印輸出時都會在指定的文件內打印輸出
16                     //在這裏將打印輸出窗口設置到了log.txt這個文件裏面,所以打印出來的內容會在log.txt這個文件裏面看到
17                 }
18             for(char c=0;c<=60000;c++){
19                     System.out.print(c+"\t");//把世界各國的文字打印到log.txt這個文件中去
20                 }
21         }catch(Exception e){
22             e.printStackTrace();
23         }
24     }
25 }

  

9.5. 對象流——Object

  

測試代碼:

 1 package cn.gacl.test;
 2 
 3 import java.io.*;
 4 
 5 public class TestObjectIo {
 6     public static void main(String args[]) {
 7         T t = new T();
 8         t.k = 8;// 把k的值修改爲8
 9         try {
10             FileOutputStream fos = new FileOutputStream(
11                     "D:/java/TestObjectIo.txt");
12             ObjectOutputStream oos = new ObjectOutputStream(fos);
13             // ObjectOutputStream流專門用來處理Object的,在fos流的外面套接ObjectOutputStream流就可以直接把一個Object寫進去
14             oos.writeObject(t);// 直接把一個t對象寫入到指定的文件裏面
15             oos.flush();
16             oos.close();
17             FileInputStream fis = new FileInputStream(
18                     "D:/java/TestObjectIo.txt");
19             ObjectInputStream ois = new ObjectInputStream(fis);
20             // ObjectInputStream專門用來讀一個Object的
21             T tRead = (T) ois.readObject();
22             // 直接把文件裏面的內容全部讀取出來然後分解成一個Object對象,並使用強制轉換成指定類型T
23             System.out.print(tRead.i + "\t" + tRead.j + "\t" + tRead.d + "\t"
24                     + tRead.k);
25             ois.close();
26         } catch (Exception e) {
27             e.printStackTrace();
28         }
29     }
30 }
31 
32 /*
33  * 凡是要將一個類的對象序列化成一個字節流就必須實現Serializable接口
34  * Serializable接口中沒有定義方法,Serializable接口是一個標記性接口,用來給類作標記,只是起到一個標記作用。
35  * 這個標記是給編譯器看的,編譯器看到這個標記之後就可以知道這個類可以被序列化 如果想把某個類的對象序列化,就必須得實現Serializable接口
36  */
37 class T implements Serializable {
38     // Serializable的意思是可以被序列化的
39     int i = 10;
40     int j = 9;
41     double d = 2.3;
42     int k = 15;
43     // transient int k = 15;
44     // 在聲明變量時如果加上transient關鍵字,那麼這個變量就會被當作是透明的,即不存在。
45 }

  直接實現Serializable接口的類是JDK自動把這個類的對象序列化,而如果實現public interface Externalizable extends Serializable的類則可以自己控制對象的序列化,建議能讓JDK自己控制序列化的就不要讓自己去控制。

十、IO流總結

  

版權聲明:本文爲蔣大帥原創文章,轉發使用記得給點評論點贊哦
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章