1. 程式人生 > >Java提供的序列化和反序列化

Java提供的序列化和反序列化

輸出結果 one zab lasso 文件中 得到 變量 有一個 pat

序列化:是指將Java對象轉換為二進制數據。

反序列化:將二進制數據轉換為Java對象。

與序列化功能相關的類有:

  • java.io.Serializable;
  • java.io.ObjectOutputStream(用於序列化)
  • java.io.ObjectInputStream(用於反序列化)

序列化對象的前提:

  • 該對象所屬的類實現了 java.io.Serializable 接口
  • 該類的成員變量中有一個是序列化id

反序列化對象的前提:

  • 反序列化對象類也需要實現 java.io.Serializable 接口

  序列化端和反序列化端,序列化對象類和反序列化對象類

  • 兩者的類名,包名需要保持一致。否則反序列化時會拋出java.lang.ClassCastException異常。
  • 兩者的序列化id需要保持一致。否則反序列化時會拋出java.io.InvalidClassException異常。
  • 兩者中的成員變量名保持一致。

  當然,反序列化對象類可以包含額外的成員變量,也可以不包含序列化對象類中的成員變量,只不過這樣就無法讀取到該成員變量的值。

序列化對象機制的特點

  • 序列化保存的是對象的狀態,靜態變量屬於類的狀態,因此 序列化並不保存靜態變量。
  • 如果想父類對象也序列化,就需要讓父類也實現 Serializable 接口。
  • 實現 Serializable 接口的類,Array,enum 都能能被序列化。

序列化對象加密傳輸

  服務器端給客戶端發送序列化對象數據,對象中有一些數據是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時,才可以對密碼進行讀取,這樣可以一定程度保證序列化對象的數據安全。
Java序列化提供的解決方案:
  在序列化過程中,虛擬機會試圖調用對象類裏的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態改變序列化的數值。

示例:

項目A:序列化對象類:

技術分享圖片
package com.java.serializable;

import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;
import java.io.Serializable;

public class Class03 implements Serializable {
    // 序列化 ID
    private static final long serialVersionUID = 1L;
    // 序列化時不加密
    private String name;
    // 序列化時加密
    private String password="initValue";
    // 測試temp是否也能被自動序列化
    private String temp = "test value of temp";
   // 以下省略setter、getter方法

    private void writeObject(ObjectOutputStream oos) {
        try {
            PutField fields = oos.putFields();
            fields.put("password", encrypt(this.password));
            fields.put("name", this.name);
            oos.writeFields();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 將參數加密
    private String encrypt(String pwd) {
        return "encryptValue";
    }
}
View Code

項目A:序列化對象工具類

技術分享圖片
package com.java.serializable;

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

public class CtestA03 {

    public static void main(String[] args) {
        serializeObjectToFile();
    }
    // 存放Java對象二進制數據的文件
    private static final String PATH = "F:\\objFile.txt";
    // 將Java對象序列化為二進制數據存儲到文件objFile.txt
    private static void serializeObjectToFile() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(PATH));
            Class03 classObj = new Class03();
            classObj.setName("Class03.name");
            oos.writeObject(classObj);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(null != oos) {
                try {
                    oos.close();
                } catch (IOException e) {}
            }
        }
    }
}
View Code

項目B:序列化對象類

技術分享圖片
package com.java.serializable;

import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;
import java.io.Serializable;

public class Class03 implements Serializable {
    // 序列化 ID
    private static final long serialVersionUID = 1L;
    // 昵稱:序列化時不加密 
   private String name;
    // 反序列化時需要解密
    private String password="initValue";
    // 測試temp是否能通過反序列化讀取到值
    private String temp;
    // 以下省略setter、getter方法

    private void readObject(ObjectInputStream ois) {
        try {
            GetField fields = ois.readFields();
            String encryptedVar = (String) fields.get("password", "");
            this.password = decrypt(encryptedVar);
            this.name = (String) fields.get("name", "");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 解密參數
    private String decrypt(String pwd) {
        return "initValue-decrypted";
    }    
}
View Code

項目B:反序列化工具類

技術分享圖片
package com.java.serializable;

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

public class CtestB03 {
    
    public static void main(String[] args) {
        reverseSerializeFileToObject();
    }
   private static final String PATH = "F:\\objFile.txt";
    // 反序列化
    private static void reverseSerializeFileToObject() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(PATH));
            Class03 classObj = (Class03) ois.readObject();
            System.out.println("classObj.name="+classObj.getName());// classObj.name=Class03.name
            System.out.println("classObj.password="+classObj.getPassword());// classObj.password=initValue-decrypted
            System.out.println("classObj.temp="+classObj.getTemp());// classObj.temp=null
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(null != ois) {
                try {
                    ois.close();
                } catch (IOException e) {}
            }
        }
    }
}
View Code

執行main方法的結果如下:

System.out.println("classObj.name="+classObj.getName());// classObj.name=Class03.name

未加密的成員變量name,反序列化後得到的仍是序列化之前的值。

System.out.println("classObj.temp="+classObj.getTemp());// classObj.temp=null

序列化對象的成員變量temp,執行writeObject()時,沒有將該變量添加到fields中,所以沒有被序列化,反序列化後得到的值為null。

System.out.println("classObj.password="+classObj.getPassword());// classObj.password=initValue-decrypted

加密後的成員變量password,會先解密。最後讀到的是解密後的密碼值initValue-decrypted。

禁止序列化對象的成員變量

  transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中。
  在被反序列化後,transient修飾的變量的值為初始值,如 int 型的是 0,對象型的是 null。

示例:

項目A:序列化對象類

技術分享圖片
package com.java.serializable;

import java.io.Serializable;

public class Class04 implements Serializable {
    // 序列化ID
    private static final long serialVersionUID = 1L;
    // 昵稱
    private String nickName;
    // 關鍵字transient修飾,該變量無法被序列化
    private transient int age = 26;
    // 關鍵字transient修飾,該變量無法被序列化
    private transient String sex = "man";
    // 以下省略setter、getter方法
}
View Code

項目A:序列化對象工具類

技術分享圖片
package com.java.serializable;

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

public class CtestA04 {
    public static void main(String[] args) {
        serializeObjectToFile();
    }
    private static final String PATH = "F:\\objFile.txt";
    // 序列化對象,轉換成二進制數據存儲到文件objFile.txt
    private static void serializeObjectToFile() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(PATH));
            Class04 classObj = new Class04();
            classObj.setNickName("nickName");
            oos.writeObject(classObj);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(null != oos) {
                try {
                    oos.close();
                } catch (IOException e) {}
            }
        }
    }
}
View Code

項目B:反序列化對象

技術分享圖片
package com.java.serializable;

import java.io.Serializable;

public class Class04 implements Serializable {
    private static final long serialVersionUID = 1L;
    private String nickName;
    private int age;
    private String sex;
    // 以下省略setter、getter方法
}
View Code

項目B:反序列化對象工具類

技術分享圖片
package com.java.serializable;

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

public class CtestB04 {

    public static void main(String[] args) {
        reverseSerializeFileToObject();
    }
    // 存儲Java對象二進制數據的文件
    private static final String PATH = "F:\\objFile.txt";
    /**
     * 將二進制文件反序列化為java的object對象
     * 
     * 反序列化條件:
     * 1.java類的包名一致
     * 2.java類中變量名,變量類型一致
     * 3.序列化ID一致
     */
    private static void reverseSerializeFileToObject() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(PATH));
            Class04 classObj = (Class04) ois.readObject();
            System.out.println("classObj.nickName="+classObj.getNickName());
            System.out.println("classObj.age="+classObj.getAge());
            System.out.println("classObj.sex="+classObj.getSex());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(null != ois) {
                try {
                    ois.close();
                } catch (IOException e) {}
            }
        }
    }
}
View Code

執行結果如下:

System.out.println("classObj.nickName="+classObj.getNickName());// classObj.nickName=nickName;

成員變量nickName,可以正常讀取值。

System.out.println("classObj.age="+classObj.getAge());// classObj.age=0
System.out.println("classObj.sex="+classObj.getSex());// classObj.sex=null

使用transient 關鍵字修飾的成員變量age和sex,值為null,說明這兩個變量並沒有被序列化到二進制文件中。

序列化對象的存儲機制

  Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。

示例:

項目A:序列化對象類

技術分享圖片
package com.java.serializable;

import java.io.Serializable;

public class Class05 implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    // 以下省略setter、getter方法
}
View Code

項目A:序列化對象工具類

技術分享圖片
package com.java.serializable;

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

public class CtestA05 {

    public static void main(String[] args) {
        serializeObjectToFile();
    }
    private static final String PATH = "D:\\objFile.txt";
    // 將 Java 對象序列化到文件objFile.txt中
    private static void serializeObjectToFile() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(PATH));
            // 將對象兩次寫入文件
            Class05 classObj = new Class05();
            classObj.setName("classObj.name.1");
            oos.writeObject(classObj);
            oos.flush();
            classObj.setName("classObj.name.2");
            oos.writeObject(classObj);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(null != oos) {
                try {
                    oos.close();
                } catch (IOException e) {}
            }
        }
    }
}
View Code

項目B:反序列化對象

技術分享圖片
package com.java.serializable;

import java.io.Serializable;

public class Class05 implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    // 以下省略setter、getter方法
}
View Code

項目B:反序列化對象工具類

技術分享圖片
package com.java.serializable;

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

public class CtestB05 {

    public static void main(String[] args) {
        reverseSerializeFileToObject();
    }
   private static final String PATH = "D:\\objFile.txt";
    // 將二進制文件反序列化為java的object對象
    private static void reverseSerializeFileToObject() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(PATH));
            // 從文件依次讀出兩個文件
            Class05 classObj1 = (Class05) ois.readObject();
            Class05 classObj2 = (Class05) ois.readObject();
            
            /**
             * Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。
             * 反序列化時,恢復引用關系,使得classObj1 和 classObj2 指向唯一的對象,二者相等,輸出 true。該存儲規則極大的節省了存儲空間。
             */
            System.out.println("classObj1 == classObj2 : "+(classObj1 == classObj2));
            System.out.println("classObj1.name="+classObj1.getName());
            System.out.println("classObj2.name="+classObj2.getName());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(null != ois) {
                try {
                    ois.close();
                } catch (IOException e) {}
            }
        }
    }
}
View Code

輸出結果如下:

System.out.println("classObj1 == classObj2 : "+(classObj1 == classObj2));// classObj1 == classObj2 : true
System.out.println("classObj1.name="+classObj1.getName());// classObj1.name=classObj.name.1
System.out.println("classObj2.name="+classObj2.getName());// classObj1.name=classObj.name.1

  Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。反序列化時,恢復引用關系,使得classObj1 和 classObj2 指向唯一的對象,二者相等,輸出 true。該存儲規則極大的節省了存儲空間。

Java提供的序列化和反序列化