day22【Properties、ResourceBundle工具類、緩衝流、轉換流、序列化】課上

1.屬性集(掌握,很重要)

概念介紹

1.Properties,屬於雙列集合,他的父類Hashtable,jdk1.0就存在了。

2.該集合沒有泛型,因爲該集合中的存放數據類型都是String類型

3.可以將該集合的數據存儲到持久設備上(瞭解),也可以將持久設備上的數據加載到內存中(很重要)

4.Properties可以將持久設備上的數據加載到內存中,主要是加載和當前項目有關配置文件中的內容

​ 配置文件:就是一個文件(文本文件),存放一些key=value的數據,這些數據都是關於當前項目的一些信息。

​ 有關加載數據庫信息 有關配置框架信息,並且配置文件中的數據沒有漢字

特有方法

package com.itheima.sh.properties_01;

import java.util.Properties;
import java.util.Set;

/*
    構造方法:
        Properties() 創建一個無默認值的空屬性列表。
    Properties屬性集的特有方法:
        1.Object setProperty(String key, String value) 調用 Hashtable 的方法 put。添加數據
        2.String getProperty(String key) 用指定的鍵在此屬性列表中搜索屬性。 相當於 value get(key)方法
            根據鍵獲取值
        3.Set<String> stringPropertyNames()  獲取所有的鍵 相當於 keySet
 */
public class PropertiesDemo01 {
    public static void main(String[] args) {
        //1.創建集合對象 Properties() 創建一個無默認值的空屬性列表。
        Properties p = new Properties();
        //2.添加數據
        p.setProperty("張傑", "謝娜");
        p.setProperty("汪峯", "章子怡");
        p.setProperty("鄧超", "孫儷");
        p.setProperty("張傑", "柳巖");
        //3.獲取所有鍵
        Set<String> keys = p.stringPropertyNames();
        //4.取出每個鍵
        for (String key : keys) {
            //獲取值
            String value = p.getProperty(key);
            System.out.println(key+"---"+value);
        }

    }
}

小結:

1.Object setProperty(String key, String value) 調用 Hashtable 的方法 put。添加數據
2.String getProperty(String key) 用指定的鍵在此屬性列表中搜索屬性。 相當於 value get(key)方法
根據鍵獲取值
3.Set stringPropertyNames() 獲取所有的鍵 相當於 keySet

使用Properties集合讀取配置文件(開發使用)

1.使用該集合讀取的配置文件要求如下:

​ 1)必須以key=value形式存在

​ 2)不要在配置文件中書寫分號

​ 3)配置文件存在的位置可以任意,可以放到項目根目錄,還可以放到src位置(後期都放在src位置)

​ 4)不建議書寫中文,但是可以書寫

​ 5)不要書寫雙引號,因爲配置文件中的數據都是字符串

2.代碼演示:

配置文件:

username=root
password=1234
package com.itheima.sh.properties_01;

import java.io.FileInputStream;
import java.io.FileReader;
import java.util.Properties;
import java.util.Set;

/*
    使用Properties集合讀取配置文件
    使用方法:
         1.void load(InputStream inStream) 從輸入流中讀取屬性列表(鍵和元素對)。
         2.void load(Reader reader) 按簡單的面向行的格式從輸入字符流中讀取屬性列表(鍵和元素對)。
    說明:
        1.上述加載方法執行完畢之後配置文件中的數據就會被加載到屬性集對象中
 */
public class PropertiesDemo02 {
    public static void main(String[] args) throws Exception{
        //1.創建屬性集對象
        Properties p = new Properties();
        //2.使用對象調用加載方法加載配置文件的數據
//        FileInputStream fis = new FileInputStream("person.properties");
        //上述加載方法執行完畢之後配置文件中的數據就會被加載到屬性集對象中
        //1.void load(InputStream inStream) 從輸入流中讀取屬性列表(鍵和元素對)。
//        p.load(fis);
        //2.void load(Reader reader) 按簡單的面向行的格式從輸入字符流中讀取屬性列表(鍵和元素對)。
        p.load(new FileReader("person.properties"));
        //3.遍歷集合p
        Set<String> keys = p.stringPropertyNames();
        for (String key : keys) {
            System.out.println("鍵:"+key);
            System.out.println("值:"+p.getProperty(key));
        }
    }
}

小結:

1.使用屬性集Properties中的加載方法可以加載配置文件中的數據:

 1.void load(InputStream inStream) 從輸入流中讀取屬性列表(鍵和元素對)。
     	如果配置文件中有漢字不能使用,會出現亂碼
 2.void load(Reader reader) 按簡單的面向行的格式從輸入字符流中讀取屬性列表(鍵和元素對)。

2.配置文件的後綴名不一定是.properties

3.上述加載方法執行完畢之後配置文件中的數據就會被加載到屬性集對象中

4.可以將該集合的數據存儲到持久設備上(瞭解):

 void store(OutputStream out, String comments) 將集合中的數據長久保存到持久設備上
     		參數:
     				out:關聯要寫的文件的路徑
                    comments:表示配置文件的說明即註釋,不要寫中文。配置文件中的註釋使用 # 表示
 

2.ResourceBundle工具類(掌握)

  • 作用:專門用來加載當前項目以.properties 結尾的配置文件。

  • 屬於抽象類,不能創建對象,我們需要使用該工具類中的靜態方法獲取子類(PropertyResourceBundle )對象

    static ResourceBundle getBundle(String baseName) 根據指定的配置文件名獲取ResourceBundle類的對象,不需要給後綴名
        舉例:假設配置文件整體名字是:person.properties 這裏只需給person 即可
    
  • 根據配置文件中的key獲取value:

     String getString(String key)  
    
  • 使用該工具類ResourceBundle讀取的配置文件要求必須位於src下面,否則不能讀取

  • 代碼實現

    username=root
    password=1234
    
    package com.itheima.sh.resourcebundle_02;
    
    import java.util.ResourceBundle;
    
    public class Test01 {
        public static void main(String[] args) {
            //1.獲取ResourceBundle類的對象
            /*
                static ResourceBundle getBundle(String baseName) 根據指定的配置文件名獲取ResourceBundle類的對象,不需要給後綴名
                    舉例:假設配置文件整體名字是:person.properties 這裏只需給person 即可
             */
            ResourceBundle bundle = ResourceBundle.getBundle("user");
            //2.String getString(String key)   根據key獲取value
            String username = bundle.getString("username");
            String password = bundle.getString("password");
            System.out.println(username+"--"+password);//root--1234
        }
    }
    
    

    小結:

    1.獲取ResourceBundle類的對象

 static ResourceBundle getBundle(String baseName) 根據指定的配置文件名獲取ResourceBundle類的對象,不需要給後綴名
            舉例:假設配置文件整體名字是:person.properties 這裏只需給person 即可

​ 2.String getString(String key) 根據key獲取value

​ 3.ResourceBundle只能操作src下面並且後綴名是.properties

​ 4.如果配置文件中有中文(瞭解)

解決亂碼:

在這裏插入圖片描述

在這裏插入圖片描述

3.緩衝流(掌握)

之前學習的基本流用來操作文件中的數據,但是效率有可能會低一些,爲了提高讀寫效率,我們引入緩衝流.

緩衝流作用:就是爲了提高讀寫效率。

名字:在之前學習的基本流的父類前面加Buffered

1.BufferedInputStream ----> 字節輸入緩衝流

2.BufferedOutputStream ----> 字節輸出緩衝流

3.BufferedReader ---->字符輸入緩衝流

4.BufferedWriter ---->字符輸出緩衝流

1.字節緩衝流

1.BufferedInputStream ----> 字節輸入緩衝流 父類是:InputStream

BufferedInputStream(InputStream in) 參數in表示字節輸入流,關聯讀取的文件 底層有數組      

2.BufferedOutputStream ----> 字節輸出緩衝流 父類是:OutputStream

BufferedOutputStream(OutputStream out) 參數out表示字節輸出流,關聯寫的文件 底層有數組     

代碼演示:

需求:分別使用之前學習的基本流和現在的緩衝流複製文件,查看複製的時間

package com.itheima.sh.buffered_03;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/*
    需求:分別使用之前學習的基本流和現在的緩衝流複製文件,查看複製的時間
 */
public class BufferedDemo01 {
    public static void main(String[] args) throws Exception{
        method_2();
    }
    //使用緩衝流複製
    private static void method_2() throws Exception{

        //1.創建字節輸入流關聯數據源文件D:\test\制服誘惑2.mp4
        FileInputStream fis = new FileInputStream("D:\\test\\制服誘惑2.mp4");
        //BufferedInputStream(InputStream in) 參數in表示字節輸入流,關聯讀取的文件 底層有數組
        BufferedInputStream bis = new BufferedInputStream(fis);

        //2.創建字節輸出流關聯目的地文件F:\制服誘惑2.mp4
        FileOutputStream fos = new FileOutputStream("F:\\制服誘惑2.mp4");
        //BufferedOutputStream(OutputStream out) 參數out表示字節輸出流,關聯寫的文件 底層有數組

        BufferedOutputStream bos = new BufferedOutputStream(fos);

        //3.定義字節數組
        byte[] buf = new byte[1024*8];
        //4.定義變量保存每次讀取的字節個數
        int len;
        long start = System.currentTimeMillis();
        //5.使用循環讀取數據
        while((len=bis.read(buf))!=-1){
            //6.使用字節輸出流將內存中的數據寫到目的地
            bos.write(buf,0,len);
        }
        long end = System.currentTimeMillis();
        System.out.println(end -start);//191ms
        //7.關閉資源  釋放資源規律:從下往上釋放
        bos.close();
        fos.close();
        bis.close();
        fis.close();


    }

    //使用之前學習的基本流複製文件
    //將D:\test\制服誘惑2.mp4複製到F:\制服誘惑2.mp4
    private static void method_1() throws Exception{
        //1.創建字節輸入流關聯數據源文件D:\test\制服誘惑2.mp4
        FileInputStream fis = new FileInputStream("D:\\test\\制服誘惑2.mp4");
        //2.創建字節輸出流關聯目的地文件F:\制服誘惑2.mp4
        FileOutputStream fos = new FileOutputStream("F:\\制服誘惑2.mp4");
        //3.定義字節數組
        byte[] buf = new byte[1024*8];
        //4.定義變量保存每次讀取的字節個數
        int len;
        long start = System.currentTimeMillis();
        //5.使用循環讀取數據
        while((len=fis.read(buf))!=-1){
            //6.使用字節輸出流將內存中的數據寫到目的地
            fos.write(buf,0,len);
        }
        long end = System.currentTimeMillis();
        System.out.println(end -start);//640ms
        //7.關閉資源
        fos.close();
        fis.close();
    }
}

小結:

字節緩衝流作用:提高讀寫效率的。

2.字符緩衝流

3.BufferedReader ---->字符輸入緩衝流 ----父類Reader

構造方法:

BufferedReader(Reader in) 參數in表示關聯的讀取的文件路徑

特有方法:

String readLine() 讀取一個文本行放到返回值中,如果讀取到文件末尾返回null

以前基本流模板:

char[] ch = new char[1024];
int len;
while((len=fr.read(ch))!=-1){
    ......
}

高效流模板:

String line = null;//line保存每次讀取一行數據
while((line=br.readLine())!=null){
    ......
}

4.BufferedWriter ---->字符輸出緩衝流------父類Writer

構造方法:

BufferedWriter(Writer out) 參數out表示關聯的寫的文件路徑

特有方法:

void newLine() 寫入一個行分隔符。 

代碼演示:

package com.itheima.sh.buffered_03;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

/*
    字符緩衝流:
    1.BufferedReader ---->字符輸入緩衝流 ----父類Reader
        構造方法:
            BufferedReader(Reader in) 參數in表示關聯的讀取的文件路徑
        特有方法:
            String readLine() 讀取一個文本行放到返回值中,如果讀取到文件末尾返回null
    2.BufferedWriter ---->字符輸出緩衝流------父類Writer
        構造方法:
            BufferedWriter(Writer out) 參數out表示關聯的寫的文件路徑
        特有方法:
            void newLine() 寫入一個行分隔符。
 */
public class BufferedDemo02 {
    public static void main(String[] args) throws Exception{
        //需求:將D:\test\故事.txt複製到項目根目錄
        //1.創建字符輸入緩衝流對象關聯數據源文件 D:\test\故事.txt
        //BufferedReader(Reader in) 參數in表示關聯的讀取的文件路徑
        FileReader fr = new FileReader("D:\\test\\故事.txt");
        BufferedReader br = new BufferedReader(fr);
        //2.創建字符輸出緩存流對象關聯目的地文件根目錄故事.txt
        FileWriter fw = new FileWriter("故事.txt");
        BufferedWriter bw = new BufferedWriter(fw);
        //3.讀取數據
        String line = null;
        while((line=br.readLine())!=null){
            //4.寫數據
            bw.write(line);
            //換行
            bw.newLine();
            //刷新
            bw.flush();
        }
        //5.釋放資源
        bw.close();
        fw.close();
        br.close();
        fr.close();
    }
}

小結:

1.BufferedReader類提供了一次可以讀取一行數據的特有方法:String readLine()讀取文件末尾返回null

2.BufferedWriter類中提供了一個換行方法 void newLine()

3.字符緩衝流練習(課下必須完成)

package com.itheima.sh.buffered_03;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.HashMap;

/*
    需求:將123.txt中內容按順序寫到456.txt中
    分析:
        先讀取一行數據:
            String line="3.侍中、侍郎郭攸之、費禕、董允等,此皆良實,志慮忠純。。。"
         上述數據由三部分組成:
            1)序號 3
            2)點 .
            3) 文字
         按照點進行切割 String[] split("切割符號")
         line.split(".")  這裏參數的點表示任意字符 如果想表示真正的點 \\.
          String[] arr = line.split("\\.");
           數組中的數據:arr = {"3","侍中、侍郎郭攸之、費禕、董允等,此皆良實,志慮忠純。。。"}
         將數組第一個數據序號變爲整數---》3  Integer.parseInt("3") ---》 3

         將上述數組中的序號變爲整數之後,作爲Map集合的key存儲  數組的第二個數據作爲value存儲到map集合
         HashMap<Integer,String> hm = new HashMap<Integer,String>();
         hm.put(Integer.parseInt("3"),"侍中、侍郎郭攸之、費禕、董允等,此皆良實,志慮忠純。。。");

         遍歷map集合根據序號取出value然後拼接,最後使用字符輸出流寫到目的地文件456.txt中
    步驟:
    1.創建Map集合對象
    2.創建字符輸入緩衝流對象關聯數據源文件123.txt
    3.創建字符輸出緩衝流對象關聯目的地文件456.txt
    4.使用字符輸入緩衝流讀取133.txt文件,每次讀取異常數據存儲到變量line中
    5.使用變量line調用切割方法line.split("\\."); 按照點進行切割
    6.取出數組的第一個數據變爲整數存儲到map集合鍵位置,第二個數據即文字存儲到value位置
    7.遍歷map集合取出key和value
    8.將key和value進行拼接並使用字符輸出流寫到目的地文件456.txt中
    9.關閉資源
 */
public class Test01 {
    public static void main(String[] args) throws Exception {
        //1.創建Map集合對象
        HashMap<Integer, String> hm = new HashMap<Integer, String>();
        //2.創建字符輸入緩衝流對象關聯數據源文件123.txt
        BufferedReader br = new BufferedReader(new FileReader("123.txt"));

        //3.創建字符輸出緩衝流對象關聯目的地文件456.txt
        BufferedWriter bw = new BufferedWriter(new FileWriter("456.txt"));
        //4.使用字符輸入緩衝流讀取123.txt文件,每次讀取異常數據存儲到變量line中
        String line = null;
        while ((line = br.readLine()) != null) {
            // 5.使用變量line調用切割方法line.split("\\."); 按照點進行切割
            String[] arr = line.split("\\.");
            //6.取出數組的第一個數據變爲整數存儲到map集合鍵位置,第二個數據即文字存儲到value位置
            //數組中的數據:arr = {"3","侍中、侍郎郭攸之、費禕、董允等,此皆良實,志慮忠純。。。"}
            //將數組第一個數據即序號字符串3轉換爲整數
//            int key = Integer.parseInt(arr[0]);
            hm.put(Integer.parseInt(arr[0]), arr[1]);
        }
        /*
            map的數據:
                3  侍中、侍郎郭攸之
                1  先帝創業未半而中道崩殂
         */
        //7.遍歷map集合取出key和value
        /*for(int key=1;key<=hm.size();key++){
            //根據key獲取value
            String value = hm.get(key);
            // 8.將key和value進行拼接並使用字符輸出流寫到目的地文件456.txt中
            bw.write(key+"."+value);
            //換行
            bw.newLine();
            //刷新
            bw.flush();
        }*/
        for (int i = 1; i <= hm.size(); i++) {//i表示序號
            //將序號賦值給變量key
            Integer key = i;
            //根據key獲取value
            String value = hm.get(key);
            // 8.將key和value進行拼接並使用字符輸出流寫到目的地文件456.txt中
            bw.write(key + "." + value);
            //換行
            bw.newLine();
            //刷新
            bw.flush();
        }

        //9.關閉資源
        bw.close();
        br.close();
    }
}

4.轉換流(理解)

編碼表

介紹

我們可以將編碼表看作一個詞典。

hello---->你好

編碼表中:

字符(人類) 二進制(計算機)

A----------65------01000001

在這裏插入圖片描述

  • ASCII(American Standard Code for Information Interchange,美國信息交換標準代碼)支持英文字母 數字 符號。

    在ASCII碼錶中一個字符佔一個字節。

  • ISO-8859-1碼錶:支持歐洲 別名稱爲Latin1 拉丁文 內部有ASCII

    在ISO-8859-1碼錶一個字符佔一個字節

  • GBxxx字符集 :內部含有ASCII,針對漢字的碼錶

    • GB2312 支持6000多漢字
    • GBK :國標 支持幾萬
    • GB18030:支持甲骨文。。。

    在gbk碼錶中一個漢字佔2個字節,英文字母 數字 特殊符號佔一個字節

  • Unicode字符集:萬國碼表,支持所有國家的字符

    • 主要包含:UTF-8 UTF-16 UTF-32
    • 在UTF-8碼錶中:
      • 英文字母 數字 特殊符號佔1個字節
      • 歐洲字符佔2個字節
      • 大部分漢字佔3個字節
  • 重點掌握的碼錶:

    • ASCII 一個字符佔一個字節。
    • ISO-8859-1碼錶:在ISO-8859-1碼錶一個字符佔一個字節
    • GBK : 支持漢字 。在gbk碼錶中一個漢字佔2個字節,英文字母 數字 特殊符號佔一個字節
    • UTF-8:英文字母 數字 特殊符號佔1個字節 。大部分漢字佔3個字節
  • 編碼、解碼、亂碼

    • 編碼:‘A’-----65----01000001 就是將我們看的懂的字符編碼爲我們看不懂的內容

    • 解碼:01000001 -----65 ---- ‘A’ 就是將我們看不懂的內容變爲看的懂的字符

    • 亂碼:就是編碼和解碼使用的編碼表不一致導致的

      舉例;
      鎖哥----按照GBK進行編碼   鎖:2373632622
      解碼:使用UTF-8  鎖: 1726272626
         
          此時會產生亂碼:
          Γゥ
      解決亂碼:
          按照之前的編碼進行編碼回去,然後在重新解碼
          上述使用UTF-8亂碼的,我們需要使用UTF-8編碼回到之前狀態
          在使用正確的碼錶GBK解碼
      

編碼引出的問題以及解決方案

package com.itheima.sh.charset_04;

import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStreamReader;

/*
    需求:使用字符輸入流讀取E:\\1.txt文件中的數據,要求:1.txt使用GBK編碼方式保存。
    我們將數據輸出到控制檯發現出現亂碼:
        1.txt文件使用的編碼表是GBK
        而我們使用字符輸入流FileReader來讀取文件,該流使用當前環境默認的編碼表:UTF-8
        上述編碼和解碼使用編碼表不一致了產生亂碼。
    解決:不能使用FileReader類,因爲該類不能隨意指定編碼表,只能是當前環境默認的編碼表,我們可以使用其父類:
        InputStreamReader 屬於字符輸入轉換流 父類-----Reader
        使用構造方法可以指定編碼表:
        InputStreamReader(InputStream in, String charsetName)
                參數:
                    in:關聯要讀取的文件的路徑
                    charsetName:指定的編碼表
 */
public class Demo01 {
    public static void main(String[] args) throws Exception{
        //1.創建字符輸入流關聯1.txt文件
//        FileReader fr = new FileReader("E:\\1.txt");

        //創建字符輸入流並指定編碼表是GBK
        InputStreamReader fr = new InputStreamReader(new FileInputStream("E:\\1.txt"), "GBK");
        //2.讀取數據
        char[] ch = new char[1024];
        int len;
        while((len=fr.read(ch))!=-1){
            //輸出
            System.out.println(new String(ch,0,len));//你好
        }
        fr.close();
    }
}

小結:

1.FileReader表示字符輸入流的基本流,但是隻能是按照當前環境默認編碼表

2.InputStreamReader作爲FileReader的父類。表示字符輸入轉換流,可以指定任意的編碼表。

3.InputStreamReader屬於字符流,除了可以指定編碼表,還可以將字節輸入流轉換爲字符輸入流

InputStreamReader fr = new InputStreamReader(new FileInputStream("E:\\1.txt"), "GBK");

OutputStreamWriter類

1.表示字符輸出轉換流。

2.該類的父類是Writer,屬於字符流

3.該類可以指定編碼表並且可以將字節輸出流轉換爲字符輸出流

OutputStreamWriter(OutputStream out, String charsetName) 
    	參數:
    		out:表示關聯寫數據的文件路徑 屬於字節輸出流
            charsetName:指定編碼表
OutputStreamWriter(OutputStream out) 創建使用默認字符編碼的 OutputStreamWriter。

代碼演示:

package com.itheima.sh.charset_04;

import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.OutputStreamWriter;

/*
    需求:使用字符輸出轉換流寫數據,要求文件編碼表使用的是GBK
 */
public class Demo02 {
    public static void main(String[] args) throws Exception{
        //創建字符輸出的基本流 FileWriter 使用當前環境默認的編碼表即UTF-8
//        FileWriter fw = new FileWriter("D:\\aaa\\5.txt");
        /*
            OutputStreamWriter(OutputStream out, String charsetName)
                參數:
                    out:表示關聯寫數據的文件路徑 屬於字節輸出流
                    charsetName:指定編碼表
         */
        OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream("D:\\aaa\\6.txt"), "GBK");
        //寫數據
        fw.write("黑馬程序員");
        //刷新
        fw.flush();
        //釋放資源
        fw.close();
    }
}

小結:

1.OutputStreamWriter表示字符輸出轉換流,父類是Writer

2.作用:

​ 1)可以指定編碼表

​ 2)將字節輸出流轉換爲字符輸出流

  OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream("D:\\aaa\\6.txt"), "GBK");

總結:

1.只要某個流的類末尾是Stream就屬於字節流,否則就是字符流

2.末尾是Reader表示字符輸入流 ; 末尾是Witer表示字符輸出流

3.字符轉換流:

在這裏插入圖片描述

轉換流的練習(課下完成)

package com.itheima.sh.charset_04;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

/*
    需求:把D:\\out.txt以UTF-8編碼的文件轉換爲 以GBK編碼的文件
    步驟:
    1.創建字符輸入轉換流對象,指定讀取文件的編碼表是UTF-8
    2.創建字符輸出轉換流指定編碼表是GBK將內存中的數據寫到指定文件中
    3.使用輸入流讀取數據源文件D:\\out.txt
    4.使用字符輸出轉換流將數據寫到目的地文件D:\\aaa\\7.txt
    5.關閉資源
 */
public class Test01 {
    public static void main(String[] args) throws Exception{
        //1.創建字符輸入轉換流對象,指定讀取文件的編碼表是UTF-8
        InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\out.txt"), "UTF-8");
        //2.創建字符輸出轉換流指定編碼表是GBK將內存中的數據寫到指定文件中
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\aaa\\7.txt"), "GBK");
        //3.使用輸入流讀取數據源文件D:\\out.txt
        char[] ch = new char[1024];
        int len;
        while((len=isr.read(ch))!=-1){
            //4.使用字符輸出轉換流將數據寫到目的地文件D:\\aaa\\7.txt
            osw.write(ch,0,len);
            //刷新
            osw.flush();
        }
        //5.關閉資源
        osw.close();
        isr.close();
    }
}

5.序列化和反序列化流(掌握)

介紹

1.序列化流:可以將我們在程序中即內存中創建的對象長久保存到持久設備上。

Person p = new Person("鎖哥",18);

2.反序列化流:就是將之前序列化到持久設備上的對象數據讀取到內存中

使用

  • 序列化流的類:ObjectOutputStream—父類 OutputStream

    • 構造方法:

      ObjectOutputStream(OutputStream out) 參數out表示字節輸出流關聯的寫對象的文件的路徑
      
    • 寫對象方法:序列化方法

      void writeObject(Object obj) 將指定的對象寫入 ObjectOutputStream。 
      
  • 反序列化流的類:ObjectInputStream — 父類 InputStream

    • 構造方法

      ObjectInputStream(InputStream in) 參數in表示字節輸入流關聯的讀取對象所在的文件路徑
      
    • 讀對象方法:反序列化方法

       Object readObject() 從 ObjectInputStream 讀取對象。 將讀取的對象放到返回值中
      
  • 上述兩個流都是字節流

  • 序列化和反序列使用注意:

    • 被序列化或者反序列化的對象所屬類必須實現序列化接口java.io.Serializable ,該接口表示序列化接口,沒有任何成員,屬於標記性接口。標記其實現類可以被序列化和反序列化。

代碼實現:

package com.itheima.sh.object_stream_05;

import java.io.Serializable;
import java.util.stream.Stream;
/*
    被序列化或者反序列化的對象所屬類必須
    實現序列化接口java.io.Serializable ,
    該接口表示序列化接口,沒有任何成員,屬於標記性接口。
    標記其實現類可以被序列化和反序列化。
 */
public class Person implements Serializable{
    //成員變量
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


package com.itheima.sh.object_stream_05;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

/*
    序列化對象
    需求:將創建的自定義類Person的對象序列化到指定文件中
 */
public class ObjectOutputStreamDemo01 {
    public static void main(String[] args) throws Exception{
        //1.創建Person對象
        Person p = new Person("鎖哥", 19);
        //2.創建序列化流的對象
        //ObjectOutputStream(OutputStream out) 參數out表示字節輸出流關聯的寫對象的文件的路徑
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
        //3.使用對象調用序列化方法序列化對象
        //void writeObject(Object obj) 將指定的對象寫入 ObjectOutputStream。
        oos.writeObject(p);
        //4.關閉資源
        oos.close();
    }
}


package com.itheima.sh.object_stream_05;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

/*
    反序列化的
 */
public class ObjectInputStreamDemo02 {
    public static void main(String[] args) throws Exception{
        //1.創建反序列流的對象
        //ObjectInputStream(InputStream in) 參數in表示字節輸入流關聯的讀取對象所在的文件路徑
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"));
        //2.使用對象調用反序列化方法進行讀取之前序列化到硬盤中的對象
        // Object readObject() 從 ObjectInputStream 讀取對象。 將讀取的對象放到返回值中
        Person p = (Person) ois.readObject();
        //3.獲取姓名和年齡
        System.out.println(p.getName()+"----"+p.getAge());
    }
}

小結:

1.ObjectOutputStream表示序列化類,使用方法writeObject可以將任意對象寫到持久設備上

2.ObjectInputStream表示反序列化類,使用方法readObject可以讀取序列化的對象

3.被序列化或者反序列化的對象所屬類必須實現序列化標記性接口java.io.Serializable

序列化和反序列化使用細節

1.被序列化或者反序列化的對象所屬類必須實現序列化標記性接口java.io.Serializable

2.如果對象的某個成員變量的值不想被序列化到硬盤上,我們可以使用瞬態關鍵字transient修飾或者使用static修飾

//transient 表示瞬態 就是修飾的內容不會被序列化到硬盤上
    private transient String name;
    private static int age;

3.如果我們對於某個類的對象已經序列化了,然後我們又修改了該類的內容,然後不用直接序列化,而是反序列化就會出現異常:

java.io.InvalidClassException: com.itheima.sh.object_stream_05.Person; 
local class incompatible:
	stream classdesc serialVersionUID = -2950267769722938759, 
	local class serialVersionUID = -1806161995263188763
        
說明:
        報上述無效的類異常原因:該類的序列版本號與從流中讀取的類描述符的版本號不匹配 
        
        
  1.在java中如果一個類實現了序列化版本號Serializable,那麼編譯器就會根據該類的所有內容(類的成員變量 成員方法 修飾符等)在對應的.class文件中生成一個默認的版本號 serialVersionUID ,該版本號是一個long類型的,該版本號我們稱爲本地版本號:local class serialVersionUID 
        
  2.當我們使用序列化流將某個類的對象即Person對象序列化到文件中,同時也會將.class文件中的序列化版本號也寫到指定文件中,那麼此時該文件中也會有一個版本號,此版本號稱爲流中的版本號:stream classdesc serialVersionUID
        
  3.每次只要修改被序列化的類(Person),那麼就會根據新的內容在本地重新生成一個新的本地版本號,如果我們不進行序列化,直接反序列化,由於生成的序列化文件中的流版本號還是之前的,那麼就會導致本地新的版本號和流中反序列化的版本號不一致了,此時就會報異常
        
  4.每次在反序列化的時候都會拿本地版本號和流中版本號進行比對是否相等,如果不相等,則報異常InvalidClassException
        
        如果兩個版本號相等:則可以正常反序列化
        
        
  5.爲何要像上述這樣做?
        爲了安全
  6.這樣做雖然安全了,但是對於程序員來說過於敏感。所以我們在實際開發中建議在需要被序列化的類中顯示聲明一個版本號,這樣每次修改類就不會根據類的成員內容生成版本號了。
        
 public class Person implements Serializable{
    private static final long serialVersionUID = -8838871950488599879L;
 }

在這裏插入圖片描述

序列化集合

需求:我想序列化多個對象。

可以將多個對象存儲到集合中,然後序列化集合即可。

package com.itheima.sh.object_stream_05;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;

public class Test01 {
    public static void main(String[] args) throws Exception{
        //1.創建集合存儲自定義Person類的對象
        ArrayList<Person> list = new ArrayList<>();
        //2.創建Person對象
        Person p1 = new Person("鎖哥",18);
        Person p2 = new Person("巖巖",19);
        Person p3 = new Person("楊冪",20);
        Person p4 = new Person("冰冰",18);
        //3.添加數據
        Collections.addAll(list, p1, p2, p3, p4);

        //4.創建序列化對象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));
        //5.調用方法進行序列化
        oos.writeObject(list);

        //6.創建反序列化類的對象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt"));
        //7.反序列化集合
        ArrayList<Person> list2 = (ArrayList<Person>) ois.readObject();
        //8.遍歷
        for (Person p : list2) {
            System.out.println(p.getName()+"----"+p.getAge());
        }

    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章