1.序列化的概念,什么是序列化
定義
1)序列化:把對象轉化為可傳輸的字節序列過程稱為序列化。
2)反序列化:把字節序列還原為對象的過程稱為反序列化。
2.為什么要序列化?實現序列化的作用是什么?
如果光看定義我想你很難一下子理解序列化的意義,那么我們可以從另一個角度來推導出什么是序列化, 那么究竟序列化的目的是什么?
其實序列化最終的目的是為了對象可以跨平台存儲,和進行網絡傳輸。而我們進行跨平台存儲和網絡傳輸的方式就是IO,而我們的IO支持的數據格式就是字節數組。
因為我們單方面的只把對象轉成字節數組還不行,因為沒有規則的字節數組我們是沒辦法把對象的本來面目還原回來的,所以我們必須在把對象轉成字節數組的時候就制定一種規則(序列化),那么我們從IO流里面讀出數據的時候再以這種規則把對象還原回來(反序列化)。
如果我們要把一棟房子從一個地方運輸到另一個地方去,序列化就是我把房子拆成一個個的磚塊放到車子里,然后留下一張房子原來結構的圖紙,反序列化就是我們把房子運輸到了目的地以后,根據圖紙把一塊塊磚頭還原成房子原來面目的過程
3.什么情況下需要序列化?
通過上面我想你已經知道了凡是需要進行“跨平台存儲”和”網絡傳輸”的數據,都需要進行序列化。
本質上存儲和網絡傳輸 都需要經過 把一個對象狀態保存成一種跨平台識別的字節格式,然后其他的平台才可以通過字節信息解析還原對象信息。
4.序列化的方式
序列化只是一種拆裝組裝對象的規則,那么這種規則肯定也可能有多種多樣,比如現在常見的序列化方式有:
JDK(不支持跨語言)、JSON、XML、Hessian、Kryo(不支持跨語言)、Thrift、Protostuff、FST(不支持跨語言)
5.序列化技術選型的幾個關鍵點
序列化協議各有千秋,不能簡單的說一種序列化協議是最好的,只能從你的當時環境下去選擇最適合你們的序列化協議,如果你要為你的公司項目進行序列化技術的選型,那么主要從以下幾個因素。
協議是否支持跨平台
如果你們公司有好多種語言進行混合開發,那么就肯定不適合用有語言局限性的序列化協議,要不然你JDK序列化出來的格式,其他語言並沒法支持。
序列化的速度
如果序列化的頻率非常高,那么選擇序列化速度快的協議會為你的系統性能提升不少。
序列化出來的大小
如果頻繁的在網絡中傳輸的數據那就需要數據越小越好,小的數據傳輸快,也不占帶寬,也能整體提升系統的性能。
6.Java 是如何實現序列化的?
前面主要介紹了一下什么是序列化,那么下面主要講下JAVA是如何進行序列化的,以及序列化的過程中需要注意的一些問題
- java 實現序列化很簡單,只需要實現Serializable 接口即可。
public class User implements Serializable{
//年齡
private int age;
//名字
private String name ;
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;
}
}
- 把User對象設置值后寫入文件
FileOutputStream fos = new FileOutputStream("D:\\temp.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setAge(18);
user.setName("sandy");
oos.writeObject(user);
oos.flush();
oos.close();
- 再把從文件讀取出來的轉換為對象
FileInputStream fis = new FileInputStream("D:\\temp.txt");
ObjectInputStream oin = new ObjectInputStream(fis);
User user = (User) oin.readObject();
System.out.println("name="+user.getName());
輸出結果為:name=sandy
以上把User對象進行二進制的數據存儲后,並從文件中讀取數據出來轉成User對象就是一個序列化和反序列化的過程。
7.JAVA序列化中常見的問題
- 問題一:static 屬性不能被序列化
原因:序列化保存的是對象的狀態,靜態變量屬於類的狀態,因此 序列化並不保存靜態變量。
- 問題二:Transient 屬性不會被序列化
接着上面的案例,我們在User里面加上一個transient 狀態的心情屬性mood;
public class User implements Serializable {
//年齡
private int age;
//名字
private String name;
//心情
private transient String mood;
//省略get set方法
}
把User對象設置值后寫入文件
FileOutputStream fos = new FileOutputStream("D:\\temp.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setMood("愉快");
oos.writeObject(user);
oos.flush();
oos.close();
再把從文件讀取出來的轉換為對象並打印mood的值
FileInputStream fis = new FileInputStream("D:\\temp.txt");
ObjectInputStream oin = new ObjectInputStream(fis);
User user1 = (User) oin.readObject();
System.out.println("mood="+user1.getMood());
輸出結果為:mood=null(原生類型為對應類型的默認值,包裝類型為null)
- 問題三:序列化版本號serialVersionUID
所有實現序列化的對象都必須要有個版本號,這個版本號可以由我們自己定義,當我們沒定義的時候JDK工具會按照我們對象的屬性生成一個對應的版本號。
版本號有什么用?
其實這個版本號就和我們平常軟件的版本號一樣,你的軟件版本號和官方的服務器版本不一致的話就告訴你有新的功能更新了,主要用於提示用戶進行更新。序列化也一樣,我們的對象通常需要根據業務的需求變化要新增、修改或者刪除一些屬性,在我們做了一些修改后,就通過修改版本號告訴 反序列化的那一方對象有了修改你需要同步修改。
使用JDK生成的版本號和我們自定義的版本號的區別?
JDK工具生成的serialVersionUID 是根據對象的屬性信息生成的一個編號,這就意味着只要對象的屬性有一點變動那么他的序列化版本號就會同步進行改變。
這種情況有時候就不太友好,就像我們的軟件一樣,使用JDK生成的serialVersionUID,只要對象有一丁點改變serialVersionUID就會隨着變更,這樣的話用戶就得強制更新軟件的版本,用戶不更新就使用不了軟件。
而大多數友好的情況也許是這樣的,用戶可以選擇不更新,不更新的話用戶只是無法體驗新加的功能而已。
而這種方式就需要我們自定義的版本號了,這樣我就可以在新增了屬性后不修改serialVersionUID,反序列化的時候只是無法或許新加的屬性,並不影響程序運行。
下面用代碼測試一下我們的理論:
(1)對象屬性序列化版本號不同進行序列化和反序列化
繼上面的例子
序列化之前我們設置serialVersionUID=2
public class User implements Serializable {
private static final long serialVersionUID=2;
//年齡
private int age;
//名字
private String name;
}
序列化存儲User到temp.txt
FileOutputStream fos = new FileOutputStream("D:\\temp.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("sandy");
user.setAge(18);
oos.writeObject(user);
oos.flush();
oos.close();
然后我們反序列化的時候對象的版本號是serialVersionUID=1
public class User implements Serializable {
private static final long serialVersionUID=1;
//年齡
private int age;
//名字
private String name;
}
最后再把從文件數據反序列化取出來
FileInputStream fis = new FileInputStream("D:\\temp.txt");
ObjectInputStream oin = new ObjectInputStream(fis);
User user1 = (User) oin.readObject();
System.out.println("name="+user1.getName());
最后執行結果反序列化異常,原因是對象序列化和反序列化的版本號不同導致
(2)對象新增屬性,但是版本號相同也可以反序列化成功
序列化的對象信息 這里比反序列化的對象多了個SEX屬性
public class User implements Serializable {
private static final long serialVersionUID=1;
//年齡
private int age;
//名字
private String name;
//年齡
private int sex;
}
序列化存儲User到temp.txt
FileOutputStream fos = new FileOutputStream("D:\\temp.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("sandy");
user.setAge(18);
user.setSex("女");
oos.writeObject(user);
oos.flush();
oos.close();
反序列化的對象信息
序列化的對象信息 這里比序列化的對象少了個SEX屬性,但版本號一致
public class User implements Serializable {
private static final long serialVersionUID=1;
//年齡
private int age;
//名字
private String name;
}
最后再把從文件數據反序列化取出來
FileInputStream fis = new FileInputStream("D:\\temp.txt");
ObjectInputStream oin = new ObjectInputStream(fis);
User user1 = (User) oin.readObject();
System.out.println("name="+user1.getName());
最后控制台打印結果正常
結果證明,只要序列化版本一樣,對象新增屬性並不會影響反序列化對象。
(3)對象新增屬性,但是版本號是使用的JDK生成序列化版本號
省略代碼,最后執行結果報錯,原因是序列化和反序列化的版本號不一致造成。
- 問題四:父類、子類序列化問題
序列化是以正向遞歸的形式進行的,如果父類實現了序列化那么其子類都將被序列化;子類實現了序列化而父類沒實現序列化,那么只有子類的屬性會進行序列化,而父類的屬性是不會進行序列化的。
(1)父類沒有實現序列化,子類實現序列化
父類
public class Parent {
//愛好
private String like;
}
子類
public class User extends Parent implements Serializable {
//年齡
private int age;
//名字
private String name;
}
序列化后再反序列化
//序列化User對象存儲到temp.txt
FileOutputStream fos = new FileOutputStream("D:\\temp.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("sandy");
user.setAge(18);
user.setLike("看美女");
oos.writeObject(user);
oos.flush();
oos.close();
//從temp.txt 反序列化轉為User對象
FileInputStream fis = new FileInputStream("D:\\temp.txt");
ObjectInputStream oin = new ObjectInputStream(fis);
User user1 = (User) oin.readObject();
System.out.println("like="+user1.getLike());
最后執行結果,父類屬性未被序列化
(2)父類實現序列化,子類不實現序列化
父類
public class Parent implements Serializable{
//愛好
private String like;
}
子類
public class User extends Parent {
//年齡
private int age;
//名字
private String name;
}
序列化后再反序列化
//序列化User對象存儲到temp.txt
FileOutputStream fos = new FileOutputStream("D:\\temp.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setName("sandy");
user.setAge(18);
user.setLike("看美女");
oos.writeObject(user);
oos.flush();
oos.close();
//從temp.txt 反序列化轉為User對象
FileInputStream fis = new FileInputStream("D:\\temp.txt");
ObjectInputStream oin = new ObjectInputStream(fis);
User user1 = (User) oin.readObject();
System.out.println("name="+user1.getName());
最后執行結果,子類屬性序列化正常
https://zhuanlan.zhihu.com/p/40462507
https://www.cnblogs.com/wxgblogs/p/5849951.html