流是用來讀寫數據的,java有一個類叫File,它封裝的是文件的文件名,只是內存裏面的一個對象,真正的文件是在硬盤上的一塊空間,在這個文件裏面存放着各類各樣的數據,咱們想讀文件裏面的數據怎麼辦呢?是經過一個流的方式來讀,我們要想從程序讀數據,對於計算機來講,不管讀什麼類型的數據都是以010101101010這樣的形式讀取的。怎麼把文件裏面的數據讀出來呢?你能夠把文件想象成一個小桶,文件就是一個桶,文件裏面的數據就至關因而這個桶裏面的水,那麼咱們怎麼從這個桶裏面取水呢,也就是怎麼從這個文件讀取數據呢。java
常見的取水的辦法是咱們用一根管道插到桶上面,而後在管道的另外一邊打開水龍頭,桶裏面的水就開始嘩啦嘩啦地從水龍頭裏流出來了,桶裏面的水是經過這根管道流出來的,所以這根管道就叫流,JAVA裏面的流式輸入/輸出跟水流的原理如出一轍,當你要從文件讀取數據的時候,一根管道插到文件裏面去,而後文件裏面的數據就順着管道流出來,這時你在管道的另外一頭就能夠讀取到從文件流出來的各類各樣的數據了。當你要往文件寫入數據時,也是經過一根管道,讓要寫入的數據經過這根管道嘩啦嘩啦地流進文件裏面去。除了從文件去取數據之外,還能夠經過網絡,好比用一根管道把我和你的機子鏈接起來,我說一句話,經過這個管道流進你的機子裏面,你立刻就能夠看獲得,而你說一句話,經過這根管道流到個人機子裏面,我也立刻就能夠看到。有的時候,一根管道不夠用,比方說這根管道流過來的水有一些雜質,咱們就能夠在這個根管道的外面再包一層管道,把雜質給過濾掉。從程序的角度來說,從計算機讀取到的原始數據確定都是010101這種形式的,一個字節一個字節地往外讀,當你這樣讀的時候你以爲這樣的方法不合適,不要緊,你再在這根管道的外面再包一層比較強大的管道,這個管道能夠把010101幫你轉換成字符串。這樣你使用程序讀取數據時讀到的就再也不是010101這種形式的數據了,而是一些能夠看得懂的字符串了。c++
io包裏面定義了全部的流,因此一說流指的就是io包裏面的小程序
什麼叫輸入流?什麼叫輸出流?用一根管道一端插進文件里程序裏面,而後開始讀數據,那麼這是輸入仍是輸出呢?若是站在文件的角度上,這叫輸出,若是站在程序的角度上,這叫輸入。數組
記住,之後說輸入流和輸出流都是站在程序的角度上來講。網絡
你要是對原始的流不滿意,你能夠在這根管道外面再套其它的管道,套在其它管道之上的流叫處理流。爲何須要處理流呢?這就跟水流裏面有雜質,你要過濾它,你能夠再套一層管道過濾這些雜質同樣。dom
節點流就是一根管道直接插到數據源上面,直接讀數據源裏面的數據,或者是直接往數據源裏面寫入數據。典型的節點流是文件流:文件的字節輸入流(FileInputStream),文件的字節輸出流(FileOutputStream),文件的字符輸入流(FileReader),文件的字符輸出流(FileWriter)。測試
處理流是包在別的流上面的流,至關因而包到別的管道上面的管道。編碼
咱們看到的具體的某一些管道,凡是以InputStream結尾的管道,都是以字節的形式向咱們的程序輸入數據。spa
read()方法是一個字節一個字節地往外讀,每讀取一個字節,就處理一個字節。read(byte[] buffer)方法讀取數據時,先把讀取到的數據填滿這個byte[]類型的數組buffer(buffer是內存裏面的一塊緩衝區),而後再處理數組裏面的數據。這就跟咱們取水同樣,先用一個桶去接,等桶接滿水後再處理桶裏面的水。若是是每讀取一個字節就處理一個字節,這樣子讀取也太累了。命令行
以File(文件)這個類型做爲講解節點流的典型表明
範例:使用FileInputStream流來讀取FileInputStream.java文件的內容
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("文件讀取錯誤!"); } } }
範例:使用FileOutputStream流往一個文件裏面寫入數據
package cn.galc.test; import java.io.*; public class TestFileOutputStream { public static void main(String args[]) { int b = 0; FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("D:\\Java\\MyEclipse 10\\Workspaces\\AnnotationTest\\src\\cn\\galc\\test\\MyMouseAdapter.java"); out = new FileOutputStream("D:/java/TestFileOutputStream1.java"); // 指明要寫入數據的文件,若是指定的路徑中不存在TestFileOutputStream1.java這樣的文件,則系統會自動建立一個 while ((b = in.read()) != -1) { out.write(b); // 調用write(int c)方法把讀取到的字符所有寫入到指定文件中去 } in.close(); out.close(); } catch (FileNotFoundException e) { System.out.println("文件讀取失敗"); System.exit(-1);// 非正常退出 } catch (IOException e1) { System.out.println("文件複製失敗!"); System.exit(-1); } System.out .println("TestFileInputStream.java文件裏面的內容已經成功複製到文件TestFileOutStream1.java裏面"); } }
FileInputStream和FileOutputStream這兩個流都是字節流,都是以一個字節爲單位進行輸入和輸出的。因此對於佔用2個字節存儲空間的字符來講讀取出來時就會顯示成亂碼。
範例:使用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(文件)這個類型爲例對節點流進行了講解,所謂的節點流指定就是直接把輸入流或輸出插入到數據源上,直接往數據源裏面寫入數據或讀取數據。
帶有緩衝區的,緩衝區(Buffer)就是內存裏面的一小塊區域,讀寫數據時都是先把數據放到這塊緩衝區域裏面,減小io對硬盤的訪問次數,保護咱們的硬盤。能夠把緩衝區想象成一個小桶,把要讀寫的數據想象成水,每次讀取數據或者是寫入數據以前,都是先把數據裝到這個桶裏面,裝滿了之後再作處理。這就是所謂的緩衝。先把數據放置到緩衝區上,等到緩衝區滿了之後,再一次把緩衝區裏面的數據寫入到硬盤上或者讀取出來,這樣能夠有效地減小對硬盤的訪問次數,有利於保護咱們的硬盤。
package cn.gacl.test; import java.io.*; public class TestBufferStream { public static void main(String args[]) { FileInputStream fis = null; try { fis = new FileInputStream("D:/java/TestFileInputStream.java"); // 在FileInputStream節點流的外面套接一層處理流BufferedInputStream BufferedInputStream bis = new BufferedInputStream(fis); int c = 0; System.out.println((char) bis.read()); System.out.println((char) bis.read()); bis.mark(100);// 在第100個字符處作一個標記 for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) { System.out.print((char) c); } System.out.println(); bis.reset();// 從新回到原來標記的地方 for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) { System.out.print((char) c); } bis.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e1) { e1.printStackTrace(); } } }
package cn.gacl.test; import java.io.*; public class TestBufferStream1{ public static void main(String args[]){ try{ BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\java\\dat.txt")); //在節點流FileWriter的外面再套一層處理流BufferedWriter String s = null; for(int i=0;i<100;i++){ s = String.valueOf(Math.random());//「Math.random()」將會生成一系列介於0~1之間的隨機數。 // static String valueOf(double d)這個valueOf()方法的做用就是把一個double類型的數轉換成字符串 //valueOf()是一個靜態方法,因此可使用「類型.靜態方法名」的形式來調用 bw.write(s);//把隨機數字符串寫入到指定文件中 bw.newLine();//調用newLine()方法使得每寫入一個隨機數就換行顯示 } bw.flush();//調用flush()方法清空緩衝區 BufferedReader br = new BufferedReader(new FileReader("D:/java/dat.txt")); //在節點流FileReader的外面再套一層處理流BufferedReader while((s = br.readLine())!=null){ //使用BufferedReader處理流裏面提供String readLine()方法讀取文件中的數據時是一行一行讀取的 //循環結束的條件就是使用readLine()方法讀取數據返回的字符串爲空值後則表示已經讀取到文件的末尾了。 System.out.println(s); } bw.close(); br.close(); }catch(Exception e){ e.printStackTrace(); } } }
程序的輸入指的是把從文件讀取到的內容存儲到爲程序分配的內存區域裏面去。流,什麼是流,流無非就是兩根管道,一根向裏,一根向外,向裏向外都是對於咱們本身寫的程序來講,流分爲各類各樣的類型,不一樣的分類方式又能夠分爲不一樣的類型,根據方向來分,分爲輸入流和輸出流,根據讀取數據的單位的不一樣,又能夠分爲字符流和字節流,除此以外,還能夠分爲節點流和處理流,節點流就是直接和數據源鏈接的流,處理流就是包在其它流上面的流,處理流不是直接和數據源鏈接,而是從數據源讀取到數據之後再經過處理流處理一遍。緩衝流也包含了四個類:BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter。流都是成對的,沒有流是是不成對的,確定是一個in,一個out。
轉換流很是的有用,它能夠把一個字節流轉換成一個字符流,轉換流有兩種,一種叫InputStreamReader,另外一種叫OutputStreamWriter。InputStream是字節流,Reader是字符流,InputStreamReader就是把InputStream轉換成Reader。OutputStream是字節流,Writer是字符流,OutputStreamWriter就是把OutputStream轉換成Writer。把OutputStream轉換成Writer以後就能夠一個字符一個字符地經過管道寫入數據了,並且還能夠寫入字符串。咱們若是用一個FileOutputStream流往文件裏面寫東西,得要一個字節一個字節地寫進去,可是若是咱們在FileOutputStream流上面套上一個字符轉換流,那咱們就能夠一個字符串一個字符串地寫進去。
轉換流測試代碼:
package cn.gacl.test; import java.io.*; public class TestTransform1 { public static void main(String args[]) { try { OutputStreamWriter osw = new OutputStreamWriter( new FileOutputStream("D:/java/char.txt")); osw.write("MircosoftsunIBMOracleApplet");// 把字符串寫入到指定的文件中去 System.out.println(osw.getEncoding());// 使用getEncoding()方法取得當前系統的默認字符編碼 osw.close(); osw = new OutputStreamWriter(new FileOutputStream( "D:\\java\\char.txt", true), "ISO8859_1"); // 若是在調用FileOutputStream的構造方法時沒有加入true,那麼新加入的字符串就會替換掉原來寫入的字符串,在調用構造方法時指定了字符的編碼 osw.write("MircosoftsunIBMOracleApplet");// 再次向指定的文件寫入字符串,新寫入的字符串加入到原來字符串的後面 System.out.println(osw.getEncoding()); osw.close(); } catch (Exception e) { e.printStackTrace(); } } }
package cn.gacl.test; import java.io.*; public class TestTransform2{ public static void main(String args[]){ try{ InputStreamReader isr = new InputStreamReader(System.in); //System.in這裏的in是一個標準的輸入流,用來接收從鍵盤輸入的數據 BufferedReader br = new BufferedReader(isr); String s = null; s = br.readLine();//使用readLine()方法把讀取到的一行字符串保存到字符串變量s中去 while(s != null){ System.out.println(s.toUpperCase());//把保存在內存s中的字符串打印出來 s = br.readLine();//在循環體內繼續接收從鍵盤的輸入 if(s.equalsIgnoreCase("exit")){ //只要輸入exit循環就結束,就會退出 break; } } }catch(Exception e){ e.printStackTrace(); } } }
數據流測試代碼:
package cn.gacl.test; import java.io.*; public class TestDataStream{ public static void main(String args[]){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); //在調用構造方法時,首先會在內存裏面建立一個ByteArray字節數組 DataOutputStream dos = new DataOutputStream(baos); //在輸出流的外面套上一層數據流,用來處理int,double類型的數 try{ dos.writeDouble(Math.random());//把產生的隨機數直接寫入到字節數組ByteArray中 dos.writeBoolean(true);//布爾類型的數據在內存中就只佔一個字節 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); System.out.println(bais.available()); DataInputStream dis = new DataInputStream(bais); System.out.println(dis.readDouble());//先寫進去的就先讀出來,調用readDouble()方法讀取出寫入的隨機數 System.out.println(dis.readBoolean());//後寫進去的就後讀出來,這裏面的讀取順序不能更改位置,不然會打印出不正確的結果 dos.close(); bais.close(); }catch(Exception e){ e.printStackTrace(); } } }
經過bais這個流往外讀取數據的時候,是一個字節一個字節地往外讀取的,所以讀出來的數據沒法判斷是字符串仍是bool類型的值,所以要在它的外面再套一個流,經過dataInputStream把讀出來的數據轉換就能夠判斷了。注意了:讀取數據的時候是先寫進去的就先讀出來,所以讀ByteArray字節數組數據的順序應該是先把佔8個字節的double類型的數讀出來,而後再讀那個只佔一個字節的boolean類型的數,由於double類型的數是先寫進數組裏面的,讀的時候也要先讀它。這就是所謂的先寫的要先讀。若是先讀Boolean類型的那個數,那麼讀出來的狀況可能就是把double類型數的8個字節裏面的一個字節讀了出來。
測試代碼:
package cn.gacl.test; /*這個小程序是從新設置打印輸出的窗口, * 把默認在命令行窗口輸出打印內容設置成其餘指定的打印顯示窗口 */ import java.io.*; public class TestPrintStream{ public static void main(String args[]){ PrintStream ps = null; try{ FileOutputStream fos = new FileOutputStream("D:/java/log.txt"); ps = new PrintStream(fos);//在輸出流的外面套接一層打印流,用來控制打印輸出 if(ps != null){ System.setOut(ps);//這裏調用setOut()方法改變了輸出窗口,之前寫System.out.print()默認的輸出窗口就是命令行窗口. //但如今使用System.setOut(ps)將打印輸出窗口改爲了由ps指定的文件裏面,經過這樣設置之後,打印輸出時都會在指定的文件內打印輸出 //在這裏將打印輸出窗口設置到了log.txt這個文件裏面,因此打印出來的內容會在log.txt這個文件裏面看到 } for(char c=0;c<=60000;c++){ System.out.print(c+"\t");//把世界各國的文字打印到log.txt這個文件中去 } }catch(Exception e){ e.printStackTrace(); } } }
測試代碼:
package cn.gacl.test; import java.io.*; public class TestObjectIo { public static void main(String args[]) { T t = new T(); t.k = 8;// 把k的值修改成8 try { FileOutputStream fos = new FileOutputStream( "D:/java/TestObjectIo.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); // ObjectOutputStream流專門用來處理Object的,在fos流的外面套接ObjectOutputStream流就能夠直接把一個Object寫進去 oos.writeObject(t);// 直接把一個t對象寫入到指定的文件裏面 oos.flush(); oos.close(); FileInputStream fis = new FileInputStream( "D:/java/TestObjectIo.txt"); ObjectInputStream ois = new ObjectInputStream(fis); // ObjectInputStream專門用來讀一個Object的 T tRead = (T) ois.readObject(); // 直接把文件裏面的內容所有讀取出來而後分解成一個Object對象,並使用強制轉換成指定類型T System.out.print(tRead.i + "\t" + tRead.j + "\t" + tRead.d + "\t" + tRead.k); ois.close(); } catch (Exception e) { e.printStackTrace(); } } } /* * 凡是要將一個類的對象序列化成一個字節流就必須實現Serializable接口 * Serializable接口中沒有定義方法,Serializable接口是一個標記性接口,用來給類做標記,只是起到一個標記做用。 * 這個標記是給編譯器看的,編譯器看到這個標記以後就能夠知道這個類能夠被序列化 若是想把某個類的對象序列化,就必須得實現Serializable接口 */ class T implements Serializable { // Serializable的意思是能夠被序列化的 int i = 10; int j = 9; double d = 2.3; int k = 15; // transient int k = 15; // 在聲明變量時若是加上transient關鍵字,那麼這個變量就會被看成是透明的,即不存在。 }
直接實現Serializable接口的類是JDK自動把這個類的對象序列化,而若是實現public interface Externalizable extends Serializable的類則能夠本身控制對象的序列化,建議能讓JDK本身控制序列化的就不要讓本身去控制。