Java的序列化和反序列化
Java的序列化和反序列化
概述
Java物件的序列化和反序列化,這個詞對我來說追溯到大學階段,學Java物件流時知道有這東西。老師告訴我們可以把Java物件化作位元組流,儲存檔案或網路通訊。然後就是巴啦巴拉,一臉懵逼。舉個例子,有一臺北京的Java虛擬機器現在執行的某個物件要呼叫一臺在長春執行的Java虛擬機器內的某個物件,這是兩個不同的Java虛擬機器程式,我們沒辦法直接傳遞物件的引用,現在我們只能把長春的這個物件序列化,變成一塊一塊碎片,傳給北京的虛擬機器,北京虛擬機器反序列化後就造出了一個物件,然後就可以正常使用。說得通俗點,這個序列化就是跨程式資料傳輸。
序列化(Serializable介面)
要序列化的類通過實現java.io.Serializable介面啟動序列化的功能,如果它有子類,所有的子類本身也都可序列化。
Person類
public class Person implements Serializable
{
private String name;
private int age;
private String sex;
public Person(String name,int age,String sex)
{
this.name = name;
this.age = age;
this.sex = sex;
}
public int getAge()
{
return age;
}
public String getName()
{
return name;
}
public String getSex()
{
return sex;
}
@Override
public String toString()
{
return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex;
}
}
serializable介面沒有函式或者欄位,我們可以看到我們implements介面,沒實現任何的函式,它僅僅用於標識可序列化,如果我們沒有實現這個標識介面而進行序列化,會丟擲一個NotSerializableException異常。
Main類
public class Main
{
public static void main(String[] args)
{
serializePerson();
}
private static void serializePerson()
{
try {
Person customer = new Person("張三",15,"男");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:/Person"));
objectOutputStream.writeObject(customer);
System.out.println("Person序列化完成。。。");
objectOutputStream.close();
}catch (Exception e){
e.printStackTrace();
System.out.println("Person序列化出錯。。。");
}
}
}
輸出
Person序列化完成。。
在 E盤下就會有一個Person檔案,用notepad++開啟,依稀可以見到一些熟悉的字眼
我們用二進位制檢視器開啟這個檔案
左邊第一個部分是序列化的檔案頭AC ED 00 05,其他還有關於序列化的類描述,裡面的各個屬性值,還有父類的資訊,lz實在看不懂了,有大佬分析過序列化檔案,有興趣可自行百度檢視。
反序列化
Main類新增DeserializePerson函式
private static Object DeserializePerson()
{
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:/Person")));
Object object = objectInputStream.readObject();
System.out.println("反序列化完成。。。");
return object;
} catch (Exception e)
{
e.printStackTrace();
}finally
{
if (objectInputStream != null )
{
try
{
objectInputStream.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
return null;
}
輸出
反序列化完成。。。
姓名:張三,年齡:15,性別:男
serialVersionUID(標識)
知道serializable是標識的語義,這個標識是在哪?如果我們沒特意指定,在編譯過程中Java編譯器會預設賦予它一個獨一無二的編號,保證它是唯一的。但這樣做是否會給我們帶來影響?
Person類
public class Person implements Serializable
{
private String name;
private int age;
private String sex;
//新增了一個屬性
private String number;
public Person(String name,int age,String sex)
{
this.name = name;
this.age = age;
this.sex = sex;
}
public int getAge()
{
return age;
}
public String getName()
{
return name;
}
public String getSex()
{
return sex;
}
@Override
public String toString()
{
return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex;
}
}
Main類
public class Main
{
public static void main(String[] args)
{
//serializePerson();
Person person = (Person) DeserializePerson();
System.out.println(person);
}
private static Object DeserializePerson()
{
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:/Person")));
Object object = objectInputStream.readObject();
System.out.println("反序列化完成。。。");
return object;
} catch (Exception e)
{
e.printStackTrace();
}finally
{
if (objectInputStream != null )
{
try
{
objectInputStream.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
return null;
}
private static void serializePerson()
{
try {
Person customer = new Person("張三",15,"男");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:/Person"));
objectOutputStream.writeObject(customer);
System.out.println("Person序列化完成。。。");
objectOutputStream.close();
}catch (Exception e){
e.printStackTrace();
System.out.println("Person序列化出錯。。。");
}
}
}
發現丟擲了一個異常
本地的檔案流中的class(序列化)和修改完的Person.class,不相容了(UID),處於安全機制考慮,程式丟擲錯誤,拒絕載入。如何保證UID版本一致,那隻能自己指定UID,在序列化後,去新增欄位或者函式,就不會影響後期還原。
Person類
public class Person implements Serializable
{
private static final long serialVersionUID = 55555L;
private String name;
private int age;
private String sex;
public Person(String name,int age,String sex)
{
this.name = name;
this.age = age;
this.sex = sex;
}
public int getAge()
{
return age;
}
public String getName()
{
return name;
}
public String getSex()
{
return sex;
}
@Override
public String toString()
{
return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex;
}
}
Main類
public class Main
{
public static void main(String[] args)
{
serializePerson();
Person person = (Person) DeserializePerson();
System.out.println(person);
}
private static Object DeserializePerson()
{
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:/Person")));
Object object = objectInputStream.readObject();
System.out.println("反序列化完成。。。");
return object;
} catch (Exception e)
{
e.printStackTrace();
}finally
{
if (objectInputStream != null )
{
try
{
objectInputStream.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
return null;
}
private static void serializePerson()
{
try {
Person customer = new Person("張三",15,"男");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E:/Person"));
objectOutputStream.writeObject(customer);
System.out.println("Person序列化完成。。。");
objectOutputStream.close();
}catch (Exception e){
e.printStackTrace();
System.out.println("Person序列化出錯。。。");
}
}
}
序列化後,我們註釋掉main函式裡的serializePerson();修改Person類,新增或修改欄位,進行反序列化。
反序列化完成。。。
姓名:張三,年齡:15,性別:男
我們可以發現,由編譯器預設自動給我們生成的UID編碼,並不可控,對同一個類,A編譯器編譯,賦予一個UID的值和B編譯器編譯賦予的UID值也有可能不同,所以為了提高可控性,確定性,我們在一個可序列化的類中應該明確為它賦值。
Externalizable介面
以上我們可以發現,所有的序列化操作都是預設的,自動幫我們完成。但有時我們並不想這樣,有些屬性我們並不想序列化,想要自定義的方式去序列化它。為此,Java提供了一個Externalizable介面,方便使用者自定義序列化過程,它和Serializable有什麼區別?
Person類
public class Person implements Externalizable
{
private static final long serialVersionUID = 55555L;
private String name;
private int age;
private String sex;
public Person()
{
}
public Person(String name,int age,String sex)
{
this.name = name;
this.age = age;
this.sex = sex;
}
public int getAge()
{
return age;
}
public String getName()
{
return name;
}
public String getSex()
{
return sex;
}
@Override
public String toString()
{
return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException
{
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
}
}
Main類不變,輸出
Person序列化完成。。。
反序列化完成。。。
姓名:null,年齡:0,性別:null
與serialization介面相比,我們很快就能看出,Externalizable介面對Person類進行序列化和反序列化之後得到的物件的狀態並沒有儲存下來,所有屬性的值都變成預設值。它們之間有什麼關係和區別?
Externalizable繼承了Serializable,它定義了兩個抽象函式,writeExternal和readExternal,我們進行序列化和反序列需要重寫,可以指定序列化哪些屬性。
Externalizable序列化的類必須有一個無參建構函式,否則會報錯。因為Externalizable序列化的時候,讀取物件時,會呼叫無參建構函式建立一個新的物件,之後將儲存物件的欄位的值填充到新物件中。
修改Person類,重新序列化
@Override
public void writeExternal(ObjectOutput out) throws IOException
{
out.writeObject(name);
out.writeObject(age);
out.writeObject(sex);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
this.name = (String)in.readObject();
this.age = (int)in.readObject();
this.sex = (String)in.readObject();
}
輸出
Person序列化完成。。。
反序列化完成。。。
姓名:張三,年齡:15,性別:男
重寫完兩個函式,發現物件持久化完成。但細心的小夥伴可能會發現,我們序列化的成員變數都是例項變數。就會有一個疑問,換成靜態變數試試?
靜態變數被序列化?
其實序列化(預設序列化)被不儲存靜態變數,因為靜態變數屬於類本身,物件序列化,顧名思義就是指的物件本身狀態,並不包含靜態變數。
public class Person implements Serializable
{
private static final long serialVersionUID = 55555L;
private String name;
private int age;
private String sex;
private static String money;
public Person(String name,int age,String sex,String money)
{
this.name = name;
this.age = age;
this.sex = sex;
this.money = money;
}
public int getAge()
{
return age;
}
public String getName()
{
return name;
}
public String getSex()
{
return sex;
}
@Override
public String toString()
{
return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex + ",資產:" + money;
}
}
Main類
public class Main {
public static void main(String[] args) throws Exception {
serializablePerson();
Person person = (Person)DeserializablePerson();
System.out.println(person);
}
//演示使用,並不規範
private static void serializablePerson() throws Exception {
Person person = new Person("張三",15,"男","5000000");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/Person"));
objectOutputStream.writeObject(person);
objectOutputStream.close();
System.out.println("序列化完成。。。");
}
private static Object DeserializablePerson() throws Exception
{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D:/Person")));
Object object = objectInputStream.readObject();
objectInputStream.close();
System.out.println("反序列完成。。。");
return object;
}
}
輸出
序列化完成。。。
反序列完成。。。
姓名:張三,年齡:15,性別:男,資產:5000000
結果跟我們的結論出乎意料,靜態變數被序列化了,真的是這樣嗎?導致這個原因是因為我們測試都是在一個程式裡面的。JVM把money這個變數載入進來了,所以導致我們看到的是載入過的money。我們可以這樣做,多寫一個Main類,讓JVM退出後,重新載入。
MainTest類
public class MainTest {
public static void main(String[] args) throws Exception {
Person person = (Person)DeserializablePerson();
System.out.println(person);
}
private static Object DeserializablePerson() throws Exception
{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D:/Person")));
Object object = objectInputStream.readObject();
objectInputStream.close();
System.out.println("反序列化完成。。。");
return object;
}
}
現在先執行Main類,得到的是我們上面的介面,現在執行MainTest類
反序列化完成。。。
姓名:張三,年齡:15,性別:男,資產:null
可以發現靜態成員變數並沒有被儲存下來,變成一個預設值。
transient關鍵字(預設序列化)
有時候我們並不想自定義序列化,然而有些成員變數我們也不想序列化。那麼transient這個關鍵字就是你的不二人選,它的作用很簡單,就是控制變數的序列化,在變數宣告前加上這個關鍵字即可。
Person類
public class Person implements Serializable
{
private static final long serialVersionUID = 55555L;
private String name;
private int age;
private String sex;
private static String money;
//銀行賬戶
private transient String bankNumber;
//銀行密碼
private transient int passWord;
public Person(String name,int age,String sex,String money,String bankNumber,int passWord)
{
this.name = name;
this.age = age;
this.sex = sex;
this.money = money;
this.bankNumber = bankNumber;
this.passWord = passWord;
}
public int getAge()
{
return age;
}
public String getName()
{
return name;
}
public String getSex()
{
return sex;
}
@Override
public String toString()
{
return "姓名:" + this.name + ",年齡:" + this.age + ",性別:" + this.sex + ",資產:" + money + ",我的銀行賬戶是:" + this.bankNumber + ",我的銀行密碼:" + this.passWord;
}
}
Main類
public class Main {
public static void main(String[] args) throws Exception {
serializablePerson();
Person person = (Person)DeserializablePerson();
System.out.println(person);
}
//演示使用,並不規範
private static void serializablePerson() throws Exception {
Person person = new Person("張三",15,"男","5000000","564654979797464646",123456);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/Person"));
objectOutputStream.writeObject(person);
objectOutputStream.close();
System.out.println("序列化完成。。。");
}
private static Object DeserializablePerson() throws Exception
{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D:/Person")));
Object object = objectInputStream.readObject();
objectInputStream.close();
System.out.println("反序列完成。。。");
return object;
}
}
輸出
序列化完成。。。
反序列完成。。。
姓名:張三,年齡:15,性別:男,資產:5000000,我的銀行賬戶是:null,我的銀行密碼:0
這些關鍵資訊就都不會被序列化到檔案中,當然我有500w的話
序列化的儲存規則
Java序列化為了節省儲存空間,有特定的儲存規則,寫入檔案為同一物件的時候,並不會再將物件的內容儲存,而只是再次儲存一份引用。
Main類
public class Main {
public static void main(String[] args) throws Exception {
serializablePerson();
Person person = (Person) DeserializablePerson();
System.out.println(person);
}
//演示使用,並不規範
private static void serializablePerson() throws Exception {
Person person = new Person("張三",15,"男","5000000","564654979797464646",123456);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/Person"));
objectOutputStream.writeObject(person);
objectOutputStream.flush();
System.out.println(new File("D:/Person").length());
objectOutputStream.writeObject(person);
objectOutputStream.close();
System.out.println(new File("D:/Person").length());
System.out.println("序列化完成。。。");
}
private static Object DeserializablePerson() throws Exception
{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D:/Person")));
Person person =(Person) objectInputStream.readObject();
Person person1 =(Person) objectInputStream.readObject();
objectInputStream.close();
System.out.println("反序列完成。。。");
System.out.print("是否同一個物件=====>");
System.out.println(person == person1);
return person;
}
}
輸出
108
113
序列化完成。。。
反序列完成。。。
是否同一個物件=====>true
姓名:張三,年齡:15,性別:男,資產:5000000,我的銀行賬戶是:null,我的銀行密碼:0
多出五位元組儲存空間就是新增引用和一些控制資訊空間,反序列時,恢復引用關係,person和person1都指向唯一的物件,二者相等,輸出true,這樣的儲存規則就極大節省了儲存的空間。
注意事項
可以發現,我很多地方加了預設序列化的情況下,如果是自定義序列化,那麼transient這些就統統無效,是不是感覺可控性增強不少,序列化還得注意幾個點。
如果有內部類,或者是要序列化的物件的成員變數是一個物件類,那麼也必須繼承序列化的介面,否則會出錯滴。
子類即使沒有實現序列化的介面,只要父類實現了,那子類就可以直接序列化。
參考:Java序列化的高階認識
===============================================================
如發現錯誤,請及時留言,lz及時修改,避免誤導後來者。感謝!!!
原文地址https://www.cnblogs.com/dslx/p/10648414.html
相關文章
- [Java基礎]序列化和反序列化Java
- Java物件的序列化和反序列化實踐Java物件
- 從java的序列化和反序列化說起Java
- Java序列化和hessian序列化的區別Java
- Java的序列化與反序列化Java
- Java序列化、反序列化、反序列化漏洞Java
- Java中的序列化與反序列化Java
- Java基礎22--物件序列化和反序列化Java物件
- java的序列化Java
- python的序列化和反序列化Python
- PHP的序列化和反序列化入門PHP
- Java--序列化與反序列化Java
- java序列化Java
- Tinking in Java ---Java的NIO和物件序列化Java物件
- 深入分析Java的序列化與反序列化Java
- java的序列化SerializableJava
- java中的序列化Java
- Java:對一個物件序列化和反序列化的簡單實現Java物件
- .NET物件的XML序列化和反序列化物件XML
- 物件(List<T>)的序列化和反序列化物件
- [java IO流]之 序列化流和反序列化流(ObjectOutputStream/ObjectInputStream)JavaObject
- 使用 Jackson 序列化和反序列化 java.sql.BlobJavaSQL
- java 序列化與反序列化例項Java
- C++ 序列化和反序列化C++
- 什麼是Java序列化,如何實現java序列化Java
- Java安全基礎之Java序列化與反序列化Java
- 一文帶你全面瞭解java物件的序列化和反序列化Java物件
- Java物件表示方式1:序列化、反序列化和transient關鍵字的作用Java物件
- Java物件的序列化與反序列化-Json篇Java物件JSON
- Java——transient and 序列化Java
- java物件序列化Java物件
- Serializable java序列化Java
- java反序列化Java
- Java序列化(Serializable)與反序列化詳解Java
- java 物件序列化要序列化那些內容Java物件
- 序列化和反序列化pickle和json 模組JSON
- Java序列化的狀態Java
- 【Java面試】簡單說一下你對序列化和反序列化的理解Java面試