每日一博 - Java序列化一二事儿
文章目錄
- what
- Why
- 作用
- 常用API
- java.io.Serializable
- java.io.Externalizable
- java.io.ObjectOutputStream
- java.io.ObjectInputStream
- Code
- 實現Serializable接口
- ObjectOutputStream#writeObject 實現序列化
- ObjectInputStream#readObject方法實現反序列化
- 序列化底層實現分析
- writeObject(Object)
- FAQ
what
把Java對象轉換為字節序列的過程,-----------> 序列化
把字節序列恢復為Java對象的過程,-----------> 反序列化
Why
我們知道,Java對象是運行在JVM的堆內存中的,如果JVM停止后,對象也就不復存在了。
如果想在JVM停止后,把這些對象保存到磁盤或者通過網絡傳輸到另一遠程機器,怎么辦呢?------------------------------------就要把這些對象轉化為字節數組,這個過程就是序列化 .
作用
序列化使得對象可以脫離程序運行而獨立存在,它主要有兩種用途:
- 序列化機制可以讓對象地保存到磁盤上,減輕內存壓力的同時,也起了持久化的作用;
比如 Web服務器中的Session對象,當有 10+萬用戶并發訪問的,就有可能出現10萬個Session對象,內存可能消化不良,于是Web容器就會把一些seesion先序列化到硬盤中,等要用了,再把保存在硬盤中的對象還原到內存中 【僅舉例,實際工作中并不會這么干】
- 序列化機制讓Java對象在網絡中傳輸變得更加容易
在使用Dubbo遠程調用服務框架時,需要把傳輸的Java對象實現Serializable接口,即讓Java對象序列化,因為這樣才能讓對象在網絡上傳輸。
常用API
java.io.Serializable
Serializable接口是一個標記接口,沒有方法或字段。一旦實現了此接口,就標志該類的對象就是可序列化的。
public interface Serializable { }java.io.Externalizable
Externalizable繼承了Serializable接口,還定義了兩個抽象方法:writeExternal()和readExternal(),如果開發人員使用Externalizable來實現序列化和反序列化,需要重寫writeExternal()和readExternal()方法
public interface Externalizable extends java.io.Serializable {void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }java.io.ObjectOutputStream
表示對象輸出流,它的writeObject(Object obj)方法可以對指定obj對象參數進行序列化,再把得到的字節序列寫到一個目標輸出流中。
java.io.ObjectInputStream
表示對象輸入流,它的readObject()方法,從輸入流中讀取到字節序列,反序列化成為一個對象,最后將其返回。
Code
實現Serializable接口
import java.io.Serializable;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/9/12 19:07* @mark: show me the code , change the world*/ public class Artisan implements Serializable {private Integer age;private String name;public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;} }ObjectOutputStream#writeObject 實現序列化
把Artisan對象 (必須實現Serializable 接口)設置值后,寫入一個文件,即序列化
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/9/12 19:08* @mark: show me the code , change the world*/ public class Test {public static void main(String[] args) throws IOException {ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\artisan.out"));Artisan artisan = new Artisan();artisan.setAge(18);artisan.setName("artisan");objectOutputStream.writeObject(artisan);objectOutputStream.flush();objectOutputStream.close();} }ObjectInputStream#readObject方法實現反序列化
再把test.out文件讀取出來,反序列化為Student對象
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/9/12 19:09* @mark: show me the code , change the world*/ public class Test2 {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\artisan.out"));Artisan art = (Artisan) objectInputStream.readObject();System.out.println("name="+art.getName());} }序列化底層實現分析
Serializable接口,只是一個空的接口,沒有方法或字段,為什么這么神奇,實現了它就可以讓對象序列化了?
為了驗證Serializable的作用,寫個ArtisanNoSerial對象,去掉實現Serializable接口,看序列化過程怎樣吧~
堆棧信息看一下
ObjectOutputStream 在序列化的時候,會判斷被序列化的Object是哪一種類型,String array enum 還是 Serializable,如果都不是的話,拋出 NotSerializableException異常。所以 Serializable真的只是一個標志,一個序列化標志 。
writeObject(Object)
序列化的方法就是writeObject, debug下
writeObject直接調用的就是writeObject0()方法
writeObject0 主要實現是對象的不同類型,調用不同的方法寫入序列化數據,這里面如果對象實現了Serializable接口,就調用writeOrdinaryObject()方法~
private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared)throws IOException{......//調用ObjectStreamClass的寫入方法writeClassDesc(desc, false);// 判斷是否實現了Externalizable接口if (desc.isExternalizable() && !desc.isProxy()) {writeExternalData((Externalizable) obj);} else {//寫入序列化數據writeSerialData(obj, desc);}.....}writeSerialData()實現的就是寫入被序列化對象的字段數據
private void writeSerialData(Object obj, ObjectStreamClass desc)throws IOException{for (int i = 0; i < slots.length; i++) {if (slotDesc.hasWriteObjectMethod()) {//如果被序列化的對象自定義實現了writeObject()方法,則執行這個代碼塊slotDesc.invokeWriteObject(obj, this);} else {// 調用默認的方法寫入實例數據defaultWriteFields(obj, slotDesc);}}}defaultWriteFields()方法,獲取類的基本數據類型數據,直接寫入底層字節容器;獲取類的obj類型數據,循環遞歸調用writeObject0()方法,寫入數據~
private void defaultWriteFields(Object obj, ObjectStreamClass desc)throws IOException{ // 獲取類的基本數據類型數據,保存到primVals字節數組desc.getPrimFieldValues(obj, primVals);//primVals的基本類型數據寫到底層字節容器bout.write(primVals, 0, primDataSize, false);// 獲取對應類的所有字段對象ObjectStreamField[] fields = desc.getFields(false);Object[] objVals = new Object[desc.getNumObjFields()];int numPrimFields = fields.length - objVals.length;// 獲取類的obj類型數據,保存到objVals字節數組desc.getObjFieldValues(obj, objVals);//對所有Object類型的字段,循環for (int i = 0; i < objVals.length; i++) {......//遞歸調用writeObject0()方法,寫入對應的數據writeObject0(objVals[i],fields[numPrimFields + i].isUnshared());......}}FAQ
-
static靜態變量和transient 修飾的字段是不會被序列化的
-
serialVersionUID問題
JAVA序列化的機制是通過判斷類的serialVersionUID來驗證版本是否一致的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID和本地相應實體類的serialVersionUID進行比較,如果相同,反序列化成功,如果不相同,就拋出InvalidClassException異常
如果確實需要修改某個類類,又想反序列化成功,怎么辦呢?可以手動指定serialVersionUID的值,一般可以設置為1L或者,或者讓我們的編輯器IDE生成
-
如果某個序列化類的成員變量是對象類型,則該對象類型的類必須實現序列化
-
子類實現了序列化,父類沒有實現序列化,父類中的字段丟失問題
總結
以上是生活随笔為你收集整理的每日一博 - Java序列化一二事儿的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小工匠聊架构- 提升性能的大杀器之缓存技
- 下一篇: java美元兑换,(Java实现) 美元