什麼是流?流表示任何有能力產生數據的數據源對象或者是有能力接收數據的接收端對象,它屏蔽了實際的I/O設備中處理數據的細節。html
IO流是實現輸入輸出的基礎,它能夠很方便地實現數據的輸入輸出操做,即讀寫操做。java
輸入流 | 輸出流 | |
---|---|---|
字符流 | Reader | Writer |
字節流 | InputStream | OutputStream |
java1.0版本中,I/O庫中與輸入有關的全部類都將繼承InputStream
,與輸出有關的全部類繼承OutputStream
,用以操做二進制數據。linux
java1.1版本對I/O庫進行了修改:編程
ObjectInputStream
和ObjectOutputStream
。OutputStreamWriter
和InputStreamReader
。兩個不一樣的繼承層次結構擁有類似的行爲,它們都提供了讀(read)和寫(write)的方法,針對不一樣的狀況,提供的方法也是相似的。設計模式
java1.4版本的java.nio.*包中引入新的I/O類庫,這部分之後再作學習。數組
FileWriter
:自帶緩衝區,數據先寫到到緩衝區上,而後從緩衝區寫入文件。FileReader
:沒有緩衝區,能夠單個字符的讀取,也能夠自定義數組緩衝區。在實際應用中,異常處理的方式都須要按照下面的結構進行,本篇爲了節約篇幅,以後都將採用向上拋出的方式處理異常。網絡
//將流對象放在try以外聲明,並附爲null,保證編譯,能夠調用close FileWriter writer = null; try { //將流對象放在裏面初始化 writer = new FileWriter("D:\\b.txt"); writer.write("abc"); //防止關流失敗,沒有自動沖刷,致使數據丟失 writer.flush(); } catch (IOException e) { e.printStackTrace(); } finally { //判斷writer對象是否成功初始化 if(writer!=null) { //關流,不管成功與否 try { writer.close(); } catch (IOException e) { e.printStackTrace(); }finally { //不管關流成功與否,都是有意義的:標爲垃圾對象,強制回收 writer = null; } } }
writer.flush();
。close()
方法,值得注意的是,在close執行以前,流會自動進行一次flush的操做以免數據還殘存在緩衝區中,但這並不意味着flush操做是多餘的。writer!=null
時才執行關流操做。JDK1.7提出了對流進行異常處理的新方式,任何AutoClosable
類型的對象均可以用於try-with-resourses
語法,實現自動關閉。ide
要求處理的對象的聲明過程必須在try後跟的()
中,在try代碼塊以外。學習
try(FileWriter writer = new FileWriter("D:\\c.txt")){ writer.write("abc"); }catch (IOException e){ e.printStackTrace(); }
public static void main(String[] args) throws IOException { FileReader reader = new FileReader("D:\\b.txt"); //定義數組做爲緩衝區 char[] cs = new char[5]; //定義一個變量記錄每次讀取的字符 int hasRead; //讀取到末尾爲-1 while ((hasRead = reader.read(cs)) != -1) { System.out.println(new String(cs, 0, hasRead)); } reader.close(); }
m!=-1
時,終止循環。運用文件字符輸入與輸出的小小案例:this
public static void copyFile(FileReader reader, FileWriter writer) throws IOException { //利用字符數組做爲緩衝區 char[] cs = new char[5]; //定義變量記錄讀取到的字符個數 int hasRead; while((hasRead = reader.read(cs)) != -1){ //將讀取到的內容寫入新的文件中 writer.write(cs, 0, hasRead)); } reader.close(); writer.close(); }
FileOutputStream
在輸出的時候沒有緩衝區,因此不須要進行flush操做。public static void main(String[] args) throws Exception { FileOutputStream out = new FileOutputStream("D:\\b.txt"); //寫入數據 //字節輸出流沒有緩衝區 out.write("天喬巴夏".getBytes()); //關流是爲了釋放文件 out.close(); }
FileInputStream
,能夠定義字節數組做爲緩衝區。public static void main(String[] args) throws Exception{ FileInputStream in = new FileInputStream("E:\\1myblog\\Node.png"); //1.讀取字節 int i; while((i = in.read()) ! =-1) System.out.println(i); //2.定義字節數組做爲緩衝區 byte[] bs = new byte[10]; //定義變量記錄每次實際讀取的字節個數 int len; while((len = in.read(bs)) != -1){ System.out.println(new String(bs, 0, len)); } in.close(); }
BufferedRead
從Reader
對象中獲取數據提供緩衝區。public static void main(String[] args) throws IOException { //真正讀取文件的流是FileReader,它自己並無緩衝區 FileReader reader = new FileReader("D:\\b.txt"); BufferedReader br = new BufferedReader(reader); //讀取一行 //String str = br.readLine(); //System.out.println(str); //定義一個變量來記錄讀取的每一行的數據(回車) String str; //讀取到末尾返回null while((str = br.readLine())!=null){ System.out.println(str); } //關外層流便可 br.close(); }
newLine
的方法用於換行,以屏蔽不一樣操做系統的差別性。public static void main(String[] args) throws Exception { //真正向文件中寫數據的流是FileWriter,自己具備緩衝區 //BufferedWriter 提供了更大的緩衝區 BufferedWriter writer = new BufferedWriter(new FileWriter("E:\\b.txt")); writer.write("天喬"); //換行: Windows中換行是 \r\n linux中只有\n //提供newLine() 統一換行 writer.newLine(); writer.write("巴夏"); writer.close(); }
緩衝流基於裝飾設計模式,即利用同類對象構建本類對象,在本類中進行功能的改變或者加強。
例如,BufferedReader自己就是Reader對象,它接收了一個Reader對象構建自身,自身提供緩衝區和其餘新增方法,經過減小磁盤讀寫次數來提升輸入和輸出的速度。
除此以外,字節流一樣也存在緩衝流,分別是BufferedInputStream
和BufferedOutputStream
。
利用轉換流能夠實現字符流和字節流之間的轉換。
public static void main(String[] args) throws Exception { //在構建轉換流時須要傳入一個OutputStream 字節流 OutputStreamWriter ow = new OutputStreamWriter( new FileOutputStream("D:\\b.txt"),"utf-8"); //給定字符--> OutputStreamWriter轉化爲字節-->以字節流形式傳入文件FileOutputStream //若是沒有指定編碼,默認使用當前工程的編碼 ow.write("天喬巴夏"); ow.close(); }
最終與文件接觸的是字節流,意味着將傳入的字符轉換爲字節。
public static void main(String[] args) throws IOException { //以字節形式FileInputStream讀取,通過轉換InputStreamReader -->字符 //若是沒有指定編碼。使用的是默認的工程的編碼 InputStreamReader ir = new InputStreamReader( new FileInputStream("D:\\b.txt")); char[] cs = new char[5]; int len; while((len=ir.read(cs))!=-1){ System.out.println(new String(cs,0,len)); } ir.close(); }
最初與文件接觸的是字節流,意味着將讀取的字節轉化爲字符。
緩衝流基於適配器設計模式,將某個類的接口轉換另外一個用戶所但願的類的接口,讓本來因爲接口不兼容而不能在一塊兒工做的類能夠在一塊兒進行工做。
以OutputStreamWriter爲
例,構建該轉換流時須要傳入一個字節流,而寫入的數據最開始是由字符形式給定的,也就是說該轉換流實現了從字符向字節的轉換,讓兩個不一樣的類在一塊兒共同辦事。
程序的全部輸入均可以來自於標準輸入,全部輸出均可以發送到標準輸出,全部錯誤信息均可以發送到標準錯誤。
對象 | 解釋 | 封裝類型 |
---|---|---|
System.in | 標準輸入流 | InputStream |
System.out | 標準輸出流 | PrintStream |
System.err | 標準錯誤流 | PrintStream |
能夠直接使用System.out
和System.err
,可是在讀取System.in
以前必須對其進行封裝,例如咱們以前常常會使用的讀取輸入:Scanner sc = new Scanner(System.in);
實際上就封裝了System.in
對象。
/** * 從控制檯獲取一行數據 * @throws IOException readLine 可能會拋出異常 */ public static void getLine() throws IOException { //獲取一行字符數據 -- BufferedReader //從控制檯獲取數據 -- System.in //System是字節流,BufferedReader在構建的時候須要傳入字符流 //將字節流轉換爲字符流 BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); //接收標準輸入並轉換爲大寫 String str = br.readLine().toUpperCase(); //發送到標準輸出 System.out.println(str); }
經過轉換流,將System.in讀取的標準輸入字節流轉化爲字符流,發送到標準輸出,打印顯示。
打印流只有輸出流沒有輸入流
public static void main(String[] args) throws IOException { //建立PrintStream對象 PrintStream p = new PrintStream("D:\\b.txt"); p.write("abc".getBytes()); p.write("def".getBytes()); p.println("abc"); p.println("def"); //若是打印對象,默認調用對象身上的toString方法 p.println(new Object()); p.close(); }
//將System.out轉換爲PrintStream public static void main(String[] args) { //第二個參數autoFlash設置爲true,不然看不到結果 PrintWriter p = new PrintWriter(System.out,true); p.println("hello,world!"); }
SequenceInputStream
用於將多個字節流合併爲一個字節流的流。Enumeration
中來進行。InputStream
對象。以第一種構建方式爲例,咱們以前說過,Enumeration
能夠經過Vector容器的elements
方法建立。
public static void main(String[] args) throws IOException { FileInputStream in1 = new FileInputStream("D:\\1.txt"); FileInputStream in2 = new FileInputStream("D:\\a.txt"); FileInputStream in3 = new FileInputStream("D:\\b.txt"); FileInputStream in4 = new FileInputStream("D:\\m.txt"); FileOutputStream out = new FileOutputStream("D:\\union.txt"); //準備一個Vector存儲輸入流 Vector<InputStream> v = new Vector<>(); v.add(in1); v.add(in2); v.add(in3); v.add(in4); //利用Vector產生Enumeration對象 Enumeration<InputStream> e = v.elements(); //利用迭代器構建合併流 SequenceInputStream s = new SequenceInputStream(e); //讀取 byte[] bs = new byte[10]; int len; while((len = s.read(bs))!=-1){ out.write(bs,0,len); } out.close(); s.close(); }
對象序列化的目標是將對象保存在磁盤中,或容許在網絡中直接傳輸對象。對象序列化機制容許把內存中的Java對象轉換成平臺無關的二進制流,從而容許把這種二進制流持久地保存在磁盤上,經過網絡將這種二進制流傳輸到另外一個網絡節點。其餘程序一旦得到了這種流,均可以將這種二進制流恢復爲原來的Java對象。
讓某個對象支持序列化的方法很簡單,讓它實現Serializable
接口便可:
public interface Serializable { }
這個接口沒有任何的方法聲明,只是一個標記接口,代表實現該接口的類是可序列化的。
咱們一般在Web開發的時候,JavaBean可能會做爲參數或返回在遠程方法調用中,若是對象不可序列化會出錯,所以,JavaBean須要實現Serializable接口。
建立一個Person類。
//必須實現Serializable接口 class Person implements Serializable { //序列化ID serialVersionUID private static final long serialVersionUID = 6402392549803169300L; private String name; private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
建立序列化流,將對象轉化爲字節,並寫入"D:\1.data"。
public class ObjectOutputStreamDemo { public static void main(String[] args) throws IOException { Person p = new Person(); p.setAge(18); p.setName("Niu"); //建立序列化流 //真正將數據寫出的流是FileOutputStream //ObjectOutputStream將對象轉化爲字節 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\1.data")); out.writeObject(p); out.close(); } }
建立反序列化流,將從"D:\1.data"中讀取的字節轉化爲對象。
public static void main(String[] args) throws IOException, ClassNotFoundException { //建立反序列化流 //真正讀取文件的是FileInputStream //ObjectInputStream將讀取的字節轉化爲對象 ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\1.data")); //讀取數據必須進行數據類型的強制轉換 Person p = (Person)in.readObject(); in.close(); System.out.println(p.getName());//Niu System.out.println(p.getAge());//18 }
須要注意的是:
serializable
,該接口沒有任何方法,僅僅做爲標記使用。static
或transient
修飾的屬性不會進行序列化。若是屬性的類型沒有實現serializable
接口可是也沒有用這二者修飾,會拋出NotSerializableException
。InvalidClassException
。InvalidClassException
的異常。serialVersonUID
。
static final
修飾,自己必須是long
類型。// 實現writeObject和readObject兩個方法 @Data @AllArgsConstructor @NoArgsConstructor public class Person implements Serializable { private String name; private int age; // 將name的值反轉後寫入二進制流 private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } // 將讀取的字符串反轉後賦給name private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { this.name = ((StringBuffer) in.readObject()).reverse().toString(); this.age = in.readInt(); } }
還有一種更加完全的自定義機制,直接將序列化對象替換成其餘的對象,須要定義writeReplace
:
@Data @AllArgsConstructor @NoArgsConstructor public class Person implements Serializable { private String name; private int age; private Object writeReplace(){ ArrayList<Object> list = new ArrayList<>(); list.add(name); list.add(age); return list; } }
Externalizable實現了Seriablizable接口,並規定了兩個方法:
public interface Externalizable extends java.io.Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }
實現該接口,並給出兩個方法的實現,也能夠實現自定義序列化。
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Externalizable { String name; int age; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.name = ((StringBuffer) in.readObject()).reverse().toString(); this.age = in.readInt(); } }
寫在最後:若是本文有敘述錯誤之處,還望評論區批評指正,共同進步。
參考資料:《Java 編程思想》、《Java語言程序設計》、《大話設計模式》、《瘋狂Java講義》