01.緩衝流、轉換流、序列化流列印流知識梳理

2020-08-14 19:09:37

緩衝流、轉換流、序列化流列印流知識梳理

主要內容

  1. 緩衝流
  2. 轉換流
  3. 序列化流
  4. 列印流

概述

這些流都是功能更爲強大的流,是在基本的流物件的基礎上建立起來的,是對基本流的一種增強,比如能夠高效讀寫的緩衝流,能夠轉換編碼的轉換流,能夠持久化儲存物件的序列化流等等。

緩衝流

緩衝流,也叫高效流,是對4個基本的 流的增強(FileInputStream,FileOutputStream,FileReader,FileWriter),所以也是4個流,按照數據型別分類:

  • 位元組緩衝流:BufferedInputStream,BufferedOutputStream,

  • 字元緩衝流:BufferedReader,BufferedWriter

    緩衝流的基本原理,是在建立流物件時,會建立一個內建的預設大小的緩衝區陣列,通過緩衝區讀寫,減少系統IO次數,從而提高讀寫的效率。

1.1位元組緩衝流

構造方法:

  • public BufferedInputStream(InputStream in) :建立一個 新的緩衝輸入流。
  • public BufferedOutputStream(OutputStream out): 建立一個新的緩衝輸出流。

構造舉例,程式碼如下:

		//緩衝輸入流
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\圖\\a.txt"));
		//緩衝輸出流
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\圖\\ab.txt"));
效率測試,相同大小的檔案,使用基本流和緩衝流對比速度:
//緩衝流
public class mode1 {
	public static void main(String[] args) throws IOException {
		//緩衝輸入流
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\圖\\小助理封面.jpg"));
		//緩衝輸出流
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\圖\\小助理封copy.jpg"));
		long start = System.currentTimeMillis();
		int len;
		while((len=bis.read())!=-1){
			bos.write(len);
		}
		bos.flush();
		bis.close();
		bos.close();
		long end = System.currentTimeMillis();
		System.out.println(end-start + "毫秒");
		
	}

}
28毫秒
public class mode1 {
	public static void main(String[] args) throws IOException {
		//基本輸入流
		FileInputStream bis = new FileInputStream("D:\\圖\\小助理封面.jpg");
		//基本輸出流
		FileOutputStream bos = new FileOutputStream("D:\\圖\\小助理封面copy.jpg");
		long start = System.currentTimeMillis();
		int len;
		while((len=bis.read())!=-1){
			bos.write(len);
		}
		bos.flush();
		bis.close();
		bos.close();
		long end = System.currentTimeMillis();
		System.out.println(end-start + "毫秒");
		
	}

}
2803毫秒
//使用陣列的方式,更快。
public class mode1 {
	public static void main(String[] args) throws IOException {
		//緩衝基本輸入流
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\圖\\小助理封面.jpg"));
		//緩衝基本輸出流
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\圖\\小助理封copy.jpg"));
		long start = System.currentTimeMillis();
		byte[] byt = new byte[1024];
		int len;
		while((len=bis.read(byt))!=-1){
			bos.write(byt);
		}
		bos.flush();
		bis.close();
		bos.close();
		long end = System.currentTimeMillis();
		System.out.println(end-start + "毫秒");
		
	}

}
1毫秒

1.2字元緩衝流

構造方法:

  • public BufferedReader(Reader in) :建立一個 新的緩衝輸入流。
  • public BufferedWriter(Writer out): 建立一個新的緩衝輸出流。

構造舉例:

BufferedReader br = new BufferedReader(new FileReader("D:\\圖\\小助理封面.jpg"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\圖\\小助理封面copy.jpg"));

特有方法:

字元緩衝流的基本方法與普通字元流呼叫方式一致,不再闡述,我們來看它們具備的特有方法。

  • BufferedReader:public String readLine(): 讀一行文字。
  • BufferedWriter:public void newLine(): 寫一行行分隔符,由系統屬性定義符號。

方法演示:

public class mode1 {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new FileReader("D:\\圖\\a.txt"));
		//BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\圖\\小助理封面copy.jpg"));
		String str = br.readLine();
		System.out.println(str);
		br.close();
		
	}

}
10.親賢臣,遠小人,此先漢所以興隆也;親小人,遠賢臣,此後漢所以傾頹也。
public class mode1 {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new FileReader("D:\\圖\\a.txt"));
		BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\圖\\ab.txt"));
		HashMap<String,String> hs = new HashMap<>();
		String line;
		while((line = br.readLine())!=null){
			String[] arr = line.split("\\.");
			hs.put(arr[0],arr[1]);
			
		}
		System.out.println(hs);
		for(String key:hs.keySet()){
			String value = hs.get(key);
			bw.write(key+"."+value);
			bw.newLine();//此處換行newLine()方法
			
		}
		bw.flush();
		br.close();
		bw.close();
		
	}
  //原先不按順序
11.先帝在時,每與臣論此事,未嘗不嘆息痛恨於桓、靈也。侍中、尚書、長史、參軍,此悉貞良死節之臣,願陛下親之信之,則漢室之隆,可計日而待也。
12.臣本布衣,躬耕於南陽,苟全性命於亂世,不求聞達於諸侯。
1.先帝創業未半而中道崩殂,今天下三分,益州疲弊,此誠危急存亡之秋也。
2.然侍衛之臣不懈於內,忠志之士忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。
3.誠宜開張聖聽,以光先帝遺德,恢弘志士之氣,不宜妄自菲薄,引喻失義,以塞忠諫之路也。
4.宮中府中,俱爲一體,陟罰臧否,不宜異同。
5.ab周若有作奸犯科及爲忠善者,宜付有司論其刑賞,以昭陛下平明之理,不宜偏私,使內外異法也。
6.侍中、侍郎郭攸之、費禕、董允等,此皆良實,志慮忠純,是以先帝簡拔以遺陛下。
7.愚以爲宮中之事,事無大小,悉以諮之,然後施行,必能裨補闕漏,有所廣益。
8.將軍向寵,性行淑均,曉暢軍事,試用於昔日,先帝稱之曰能,是以衆議舉寵爲督。
9.愚以爲營中之事,悉以諮之,必能使行陣和睦,優劣得所。
10.親賢臣,遠小人,此先漢所以興隆也;親小人,遠賢臣,此後漢所以傾頹也。

第二章:轉換流

2.1字元編碼和字元集

計算機中儲存的資訊都是用二進制數表示的,而我們在螢幕上看到的數位、英文、標點符號、漢字等字元是二進制數轉換之後的結果。按照某種規則,將字元儲存到計算機中,稱爲編碼 。反之,將儲存在計算機中的二進制數按照某種規則解析顯示出來,稱爲解碼 。比如說,按照A規則儲存,同樣按照A規則解析,那麼就能顯示正確的文字符號。反之,按照A規則儲存,再按照B規則解析,就會導致亂碼現象。

編碼:能看懂的->二進制

解碼:二進制->能看懂的

  • 字元編碼Character Encoding** : 就是一套自然語言的字元與二進制數之間的對應規則。

    編碼表:生活中文字和計算機中二進制的對應規則

字元集

  • 字元集 Charset:也叫編碼表。是一個系統支援的所有字元的集合,包括各國家文字、標點符號、圖形符號、數位等。

計算機要準確的儲存和識別各種字元集符號,需要進行字元編碼,一套字元集必然至少有一套字元編碼。常見字元集有ASCII字元集、GBK字元集、Unicode字元集等。

可見,當指定了編碼,它所對應的字元集自然就指定了,所以編碼纔是我們最終要關心的。

  • ASCII字元集

    • ASCII(American Standard Code for Information Interchange,美國資訊交換標準程式碼)是基於拉丁字母的一套電腦編碼系統,用於顯示現代英語,主要包括控制字元(回車鍵、退格、換行鍵等)和可顯示字元(英文大小寫字元、阿拉伯數位和西文符號)。
    • 基本的ASCII字元集,使用7位(bits)表示一個字元,共128字元。ASCII的擴充套件字元集使用8位元(bits)表示一個字元,共256字元,方便支援歐洲常用字元。
  • ISO-8859-1字元集

    • 拉丁碼錶 碼表,別名Latin-1,用於顯示歐洲使用的語言,包括荷蘭、丹麥、德語、意大利語、西班牙語等。
    • ISO-8859-1使用單位元組編碼,相容ASCII編碼。
  • GBxxx字元集

    • GB就是國標的意思,是爲了顯示中文而設計的一套字元集。
    • GB2312:簡體中文碼錶 碼表。一個小於127的字元的意義與原來相同。但兩個大於127的字元連在一起時,就表示一個漢字,這樣大約可以組合了包含7000多個簡體漢字,此外數學符號、羅馬希臘的字母、日文的假名們都編進去了,連在ASCII裡本來就有的數位、標點、字母都統統重新編了兩個位元組長的編碼,這就是常說的"全形"字元,而原來在127號以下的那些就叫"半形"字元了。
    • GBK:最常用的中文碼錶 碼表。是在GB2312標準基礎上的擴充套件規範,使用了雙位元組編碼方案,共收錄了21003個漢字,完全相容GB2312標準,同時支援繁體漢字以及日韓漢字等。
    • GB18030:最新的中文碼錶 碼表。收錄漢字70244個,採用多位元組編碼,每個字可以由1個、2個或4個位元組組成。支援中國國內少數民族的文字,同時支援繁體漢字以及日韓漢字等。
  • Unicode字元集

    • Unicode編碼系統爲表達任意語言的任意字元而設計,是業界的一種標準,也稱爲統一碼、標準萬國碼。
    • 它最多使用4個位元組的數位來表達每個字母、符號,或者文字。有三種編碼方案,UTF-8、UTF-16和UTF-32。最爲常用的UTF-8編碼。
    • UTF-8編碼,可以用來表示Unicode標準中任何字元,它是電子郵件、網頁及其他儲存或傳送文字的應用中,優先採用的編碼。網際網路工程工作小組(IETF)要求所有網際網路協定都必須支援UTF-8編碼。所以,我們開發Web應用,也要使用UTF-8編碼。它使用一至四個位元組爲每個字元編碼,編碼規則:
      1. 128個US-ASCII字元,只需一個位元組編碼。
      2. 拉丁文等字元,需要二個位元組編碼。
      3. 大部分常用字(含中文),使用三個位元組編碼。
      4. 其他極少使用的Unicode輔助字元,使用四位元組編碼。

    編碼出現的問題:

    在IDEA中,使用FileReader 讀取專案中的文字檔案。由於IDEA的設定,都是預設的UTF-8編碼(我的eclipse是GBK有時候會出現亂碼),所以沒有任何問題。但是,當讀取Windows系統中建立的文字檔案時,由於Windows系統的預設是GBK編碼,就會出現亂碼。

    2.2 InputStreamReader類

    轉換流java.io.InputStreamReader,是Reader的子類,是從位元組流到字元流的橋樑。它讀取位元組,並使用指定的字元集將其解碼爲字元。它的字元集可以由名稱指定,也可以接受平臺的預設字元集。

    構造方法

    * InputStreamReader(InputStream in): 建立一個使用預設字元集的字元流。 
    * InputStreamReader(InputStream in, String charsetName): 建立一個指定字元集的字元流。
    

    構造方法舉例:

    InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
    InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
    

    指定編碼讀入:

    public class mode2 {
    	public static void main(String[] args) throws IOException, FileNotFoundException {
    		InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\圖\\b.txt"),"GBK");
    		int len = 0;
    		while((len = isr.read())!=-1){
    			System.out.println((char)len);
    		}
    		isr.close();
    	}
    }
    你
    好
    //爲啥你好是四個數
    20320
    22909
    13
    10
    

    2.3 OutputStreamWriter類

    轉換流java.io.OutputStreamWriter ,是Writer的子類,是從字元流到位元組流的橋樑。使用指定的字元集將字元編碼爲位元組。它的字元集可以由名稱指定,也可以接受平臺的預設字元集。

構造方法
  • OutputStreamWriter(OutputStream in): 建立一個使用預設字元集的字元流。
  • OutputStreamWriter(OutputStream in, String charsetName): 建立一個指定字元集的字元流。

構造舉例

OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");

指定編碼寫出

public class mode2 {
	public static void main(String[] args) throws IOException, FileNotFoundException {
	OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\圖\\zhou.txt"),"UTF-8");
	osw.write("你好漂亮");
	osw.flush();
	osw.close();
	}
}

2.4轉換檔案編碼練習:

將GBK編碼的文字檔案,轉換爲UTF-8編碼的文字檔案。

分析
  1. 指定GBK編碼的轉換流,讀取文字檔案。
  2. 使用UTF-8編碼的轉換流,寫出文字檔案。
程式碼實現:
public class mode2 {
	public static void main(String[] args) throws IOException {
		
		InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\圖\\new.txt"),"UTF-8");
		OutputStreamWriter isw = new OutputStreamWriter(new FileOutputStream("D:\\圖\\new2.txt"),"GBK");
		int len = 0;
		while((len=isr.read())!=-1){
			System.out.println((char)len);
			isw.write(len);
		}
		isr.close();
		isw.flush();
		isw.close();
		
	}
}

第三章 序列化

3.1 概述

Java 提供了一種物件序列化的機制 機製。用一個位元組序列可以表示一個物件,該位元組序列包含該物件的數據物件的型別物件中儲存的屬性等資訊。位元組序列寫出到檔案之後,相當於檔案中持久儲存了一個物件的資訊。

反之,該位元組序列還可以從檔案中讀取回來,重構物件,對它進行反序列化物件的數據物件的型別物件中儲存的數據資訊,都可以用來在記憶體中建立物件。

3.2 ObjectOutputStream類

構造方法

public ObjectOutputStream(OutputStream out): 建立一個指定OutputStream的ObjectOutputStream。

構造舉例,程式碼如下:

FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

*一個物件要想序列化,必須滿足兩個條件:

  • 該類必須實現java.io.Serializable 介面,Serializable 是一個標記介面,不實現此介面的類將不會使任何狀態序列化或反序列化,會拋出NotSerializableException

  • 該類的所有屬性必須是可序列化的。如果有一個屬性不需要可序列化的,則該屬性必須註明是瞬態的,使用transient 關鍵字修飾。

  • public class Employee implements java.io.Serializable {
        public String name;
        public String address;
        public transient int age; // transient瞬態修飾成員,不會被序列化
        public void addressCheck() {
          	System.out.println("Address  check : " + name + " -- " + address);
        }
    }
    

    2.寫出物件方法

    public final void writeObject (Object obj) : 將指定的物件寫出。

    public class mode4 {
    	public static void main(String[] args) throws FileNotFoundException, IOException {
    		personal per=  new personal("小小",21);
    		ObjectOutputStream oops = new ObjectOutputStream(new FileOutputStream("D://圖//814.txt"));
    		oops.writeObject(per);
    		System.out.println(per);
    		oops.close();
    		
    	}
    }
    personal [name=小小, id=21]
    

    3.3 ObjectInputStream類

    ObjectInputStream反序列化流,將之前使用ObjectOutputStream序列化的原始數據恢復爲物件。

    構造方法

    • public ObjectInputStream(InputStream in): 建立一個指定InputStream的ObjectInputStream。

    反序列化操作1

    如果能找到一個物件的class檔案,我們可以進行反序列化操作,呼叫ObjectInputStream讀取物件的方法:

    • public final Object readObject () : 讀取一個物件。
    public class mode5 {
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    		FileInputStream fis = new FileInputStream("D:\\圖\\814.txt");
    		ObjectInputStream ois = new ObjectInputStream(fis);
    		 personal e = (personal)ois.readObject();
    		System.out.println(e);
    		
    	}
    }
    personal [name=小小, id=21]
    
    

    對於JVM可以反序列化物件,它必須是能夠找到class檔案的類。如果找不到該類的class檔案,則拋出一個 ClassNotFoundException 異常。

    反序列化操作2

    **另外,當JVM反序列化物件時,能找到class檔案,但是class檔案在序列化物件之後發生了修改,那麼反序列化操作也會失敗,拋出一個InvalidClassException異常。**發生這個異常的原因如下:

    • 該類的序列版本號與從流中讀取的類描述符的版本號不匹配
    • 該類包含未知數據型別
    • 該類沒有可存取的無參數構造方法

    Serializable 介面給需要序列化的類,提供了一個序列版本號。serialVersionUID 該版本號的目的在於驗證序列化的物件和對應類是否版本匹配。

    報錯

    Exception in thread 「main」 java.io.InvalidClassException: text811.personal; local class incompatible: stream classdesc serialVersionUID = -3623888092351341025, local class serialVersionUID = 1

    public class Employee implements java.io.Serializable {
         // 加入序列版本號
         private static final long serialVersionUID = 1L;
         public String name;
         public String address;
         // 新增新的屬性 ,重新編譯, 可以反序列化,該屬性賦爲預設值.
         public int eid; 
    
         public void addressCheck() {
             System.out.println("Address  check : " + name + " -- " + address);
         }
    }
    

    transient

    static關鍵字,靜態關鍵字優先於非靜態關鍵字載入到記憶體中(靜態優先於物件進入到記憶體中)

    ​ 被static修飾的成員變數不能被序列化,序列化的都是物件

    private String name;
    private  static int id;
    

    transient關鍵字:瞬態關鍵字

    ​ 被transient修飾成員變數,不能被序列化,功能與static一樣,但他不是靜態

    private String name;
    private  transient int id;
    

    被static或transient修飾的成員變數反序列化時值會爲成員變數的預設值。

    3.4 練習序列化集合

    1. 將存有多個自定義物件的集合序列化操作,儲存到list.txt檔案中。
    2. 反序列化list.txt ,並遍歷集合,列印物件資訊。

    案例分析

    1. 把若幹學生物件 ,儲存到集閤中。
    2. 把集合序列化。
    3. 反序列化讀取時,只需要讀取一次,轉換爲集合型別。
    4. 遍歷集合,可以列印所有的學生資訊
    public class mode6 {
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    		ArrayList<personal> as = new ArrayList<>();
    		ArrayList<personal> aq = new ArrayList<>();
    		personal per1 = new personal("小周",21);
    		personal per2 = new personal("小趙",22);
    		personal per3 = new personal("小王",23);
    		as.add(per1);
    		as.add(per2);
    		as.add(per3);
    		FileOutputStream fos = new FileOutputStream("D:\\圖\\Student.txt");
    		ObjectOutputStream oos = new ObjectOutputStream(fos);
    		oos.writeObject(as);
    		FileInputStream fis  = new FileInputStream("D:\\圖\\Student.txt");
    		ObjectInputStream ois = new ObjectInputStream(fis);
    		
    		aq = (ArrayList<personal>)ois.readObject();
    		
    		for(int len=0;len<aq.size();len++){
    			System.out.println(aq.get(len));
    		}
            fis.close();
    		fos.close();
    		oos.close();
    		ois.close();
    		
    	}
    	結果輸出
    personal [name=小周, id=21]
    personal [name=小趙, id=22]
    personal [name=小王, id=23]
    

    第四章 列印流

    4.1 概述

    平時我們在控制檯列印輸出,是呼叫print方法和println方法完成的,這兩個方法都來自於java.io.PrintStream類,該類能夠方便地列印各種數據型別的值,是一種便捷的輸出方式。

    4.2 PrintStream類

    構造方法

    • public PrintStream(String fileName): 使用指定的檔名建立一個新的列印流。

    構造舉例,程式碼如下:

    PrintStream ps = new PrintStream("ps.txt")

    改變列印流向

    System.out就是PrintStream型別的,只不過它的流向是系統規定的,列印在控制檯上。不過,既然是流物件,改變它的流向。

    public class mode {
    	public static void main(String[] args) throws FileNotFoundException {
    		System.out.println(97);
    		PrintStream ps = new PrintStream("D:\\圖\\ps.txt");
    		System.setOut(ps);
    		System.out.println(97);
    	}
    }