日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java对象序列化详解

發布時間:2025/3/20 java 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java对象序列化详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

下面的文章在公眾號作了更新:點擊查看最新文章
可識別二維碼查看更多最新文章:

寫在前面


Java對象是在JVM中生成的,如果需要遠程傳輸或保存到硬盤上,就需要將Java對象轉換成可傳輸的文件流。
市面上目前有的幾種轉換方式:

  • 1. 利用Java的序列化功能序列成字節字節流)也就是接下來要講的。一般是需要加密傳輸時才用。
  • 2. 將對象包裝成JSON字符串字符流
    轉Json工具有Jackson、FastJson或者GJson,它們各有優缺點:
    • JackSon:Map、List的轉換可能會出現問題。轉復雜類型的Bean時,轉換的Json格式不是標準的Json格式。適合處理 大文本Json
    • FastJosn:速度最快。將復雜類型的Bean轉換成Json可能會有問題:引用類型如果沒有引用被出錯。適合對性能有要求的場景。
    • GJson:功能最全,可以將復雜的Bean和Json字符串進行互轉。性能上面比FastJson有所差距。適合處理小文本Json,和對于數據正確性有要求的場景。
  • 3. protoBuf工具(二進制)
    性能好,效率高,字節數很小,網絡傳輸節省IO。但二進制格式可讀性差。

一、定義


序列化:把Java對象轉換為字節序列的過程。
  
  反序列化:把字節序列恢復為Java對象的過程。

二、用途


  • 把對象的字節序列永久地保存到硬盤上,通常存放在一個文件中;(持久化對象
  • 在網絡上傳送對象的字節序列。(網絡傳輸對象
      
      Java平臺允許我們在內存中創建可復用的Java對象,但只有當JVM(Java虛擬機)處于運行時,這些對象才可能存在,也就是這些對象的生命周期不會比JVM的生命周期更長。但在現實應用中,就可能要求在JVM停止運行之后能夠保存指定的對象(持久化對象),并在將來重新讀取被保存的對象。
        
      網絡通信時,無論是何種類型的數據,都會轉成字節序列的形式在網絡上傳送。發送方需要把這個Java對象轉換為字節序列,才能在網絡上傳送;接收方則需要把字節序列再恢復為Java對象。

三、實現


實現了如下兩個接口之一的類的對象才能被序列化:
  
  1) Serializable
  
  2) Externalizable

序列化:ObjectOutputStream代表對象輸出流,它的writeObject(Object obj)方法可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。

反序化:ObjectInputStream代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,并將其返回。

注:使用writeObject() 和readObject()方法的對象必須已經被序列化

四、serialVersionUID


如果serialVersionUID沒有顯式生成,系統就會自動生成一個。此時,如果在序列化后我們將該類作添加或減少一個字段等的操作,系統在反序列化時會重新生成一個serialVersionUID然后去和已經序列化的對象進行比較,就會報序列號版本不一致的錯誤。為了避免這種問題, 一般系統都會要求實現serialiable接口的類顯式的生明一個serialVersionUID。

所以顯式定義serialVersionUID有如下兩種用途:
   1、 希望類的不同版本對序列化兼容時,需要確保類的不同版本具有相同的serialVersionUID;
   2、 不希望類的不同版本對序列化兼容時,需要確保類的不同版本具有不同的serialVersionUID。

五、序列化機制算法


1. 所有保存到磁盤中的對象都有一個序列化編號
  
  2. 當程序試圖序列化一個對象時,程序先檢查該對象是否已經被序列化過。如果從未被序列化過,系統就會將該對象轉換成字節序列并輸出;如果已經序列化過,將直接輸出一個序列化編號。

六、示例


要被序列化的對象對應的類的代碼:

public class Person implements Serializable { private String name = null; private Integer age = null; public Person(){System.out.println("無參構造");}public Person(String name, Integer age) { this.name = name; this.age = age; } //getter setter方法省略...@Override public String toString() { return "[" + name + ", " + age+"]"; } }

MySerilizable 是一個簡單的序列化程序,它先將一個Person對象保存到文件person.txt中,然后再從該文件中讀出被存儲的Person對象,并打印該對象。

public class MySerilizable {public static void main(String[] args) throws Exception { File file = new File("person.txt"); //序列化持久化對象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); Person person = new Person("Peter", 27); out.writeObject(person); out.close(); //反序列化,并得到對象ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); Object newPerson = in.readObject(); // 沒有強制轉換到Person類型 in.close(); System.out.println(newPerson); } }

輸出結果:

[Peter, 27]

結果沒有打印“無參構造”,說明反序列化機制無需通過構造器來初始Java對象。
  注:
  
  1.) 反序列化讀取的僅僅是Java對象的數據,而不是Java類,所以在反序列化時必須提供該Java對象所屬類的class文件(這里是Person.class),否則會引發ClassNotFoundException異常。
  
  2).當重新讀取被保存的Person對象時,并沒有調用Person的任何構造器,說明反序列化機制無須通過構造器來初始化對象。

七、選擇序列化


transient

當對某個對象進行序列化時,系統會自動將該對象的所有屬性依次進行序列化,如果某個屬性引用到別一個對象,則被引用的對象也會被序列化。如果被引用的對象的屬性也引用了其他對象,則被引用的對象也會被序列化。 這就是遞歸序列化

有時候,我們并不希望出現遞歸序列化,或是某個存敏感信息(如銀行密碼)的屬性不被序列化,我們就可通過transient關鍵字修飾該屬性來阻止被序列化。

將上面的Person類的age屬性用transient修飾:

transient private Integer age = null;

再去執行MySerilizable的結果為:

[Peter, null] //返序列化時沒有值,說明age字段未被序列化

writeObject()方法與readObject()方法

使用transient關鍵字阻止序列化雖然簡單方便,但被它修飾的屬性被完全隔離在序列化機制之外,導致了在反序列化時無法獲取該屬性的值,而通過在需要序列化的對象的Java類里加入writeObject()方法與readObject()方法可以控制如何序列化各屬性,甚至完全不序列化某些屬性(此時就transient一樣)。
  
  如果我們想要上面的Person類里的name屬性在序列化后存在文件里不讓別人知道具體是什么(加密),我們就可在Person類里加如下代碼:

//自定義序列化private void writeObject(ObjectOutputStream out) throws IOException { // out.defaultWriteObject(); // 將當前類的非靜態和非瞬態字段寫入此流。//如果不寫,如果還有其他字段,則不會被序列化out.writeObject(new StringBuffer(name).reverse());//將name簡單加密(反轉),這樣別人就知道是怎么回事,當然實際應用不可能這樣加密。out.writeInt(age); } //反序列化private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { //in.defaultReadObject();// 從此流讀取當前類的非靜態和非瞬態字段。//如果不寫,其他字段就不能被反序列化name = ((StringBuffer)in.readObject()).reverse().toString(); //解密age = in.readInt(); }

詳細的自定義序列化與反序列化可參見ObjectOutputStream 和ObjectInputStream 類的JDK文檔。

Externalizable接口

Externalizable接口 與Serializable 接口類似,只是Externalizable接口需要強制自定義序列化
要序列化對象的代碼:

public class Teacher implements Externalizable{private String name;private Integer age;public Teacher(){System.out.println("無參構造");}public Teacher(String name,Integer age){System.out.println("有參構造");this.name = name;this.age = age;}//setter、getter方法省略@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(new StringBuffer(name).reverse()); //將name簡單加密//out.writeInt(age); //注掉這句后,age屬性將不能被序化}@Overridepublic void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {name = ((StringBuffer) in.readObject()).reverse().toString();//age = in.readInt(); }@Override public String toString() { return "[" + name + ", " + age+ "]"; } }

主函數代碼改為:

public class MySerilizable {public static void main(String[] args) throws Exception { File file = new File("person.txt"); //序列化持久化對象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); Teacher person = new Teacher("Peter", 27); out.writeObject(person); out.close(); //反序列化,并得到對象ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); Object newPerson = in.readObject(); // 沒有強制轉換到Person類型 in.close(); System.out.println(newPerson); } }

打印結果:

有參構造 無參構造 //與Serializable 不同的是,還調用了無參構造 [Peter, null] //age未被序列化,所以未取到值

八、單例模式的序列化


當我們使用Singleton模式時,應該是期望某個類的實例應該是唯一的,但如果該類是可序列化的,那么情況可能略有不同。對前面使用的Person類進行修改,使其實現Singleton模式,如下所示:

public class Person implements Serializable { private static class InstanceHolder { private static final Person instatnce = new Person("John", 31, "男"); } public static Person getInstance() { return InstanceHolder.instatnce; } private String name = null; private Integer age = null; private String gender = null; private Person() { System.out.println("必須私有化的無參構造"); } private Person(String name, Integer age, String gender) { System.out.println("有參構造"); this.name = name; this.age = age; this.gender = gender; } ... }

同時要修改MySerilizable 應用,使得能夠保存/獲取上述單例對象,并進行對象相等性比較,如下代碼所示:

public class MySerilizable { public static void main(String[] args) throws Exception { File file = new File("person.txt"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); out.writeObject(Person.getInstance()); // 保存單例對象 out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); Object newPerson = in.readObject(); in.close(); System.out.println(newPerson); System.out.println(Person.getInstance() == newPerson); // 將獲取的對象與Person類中的單例對象進行相等性比較 } }

打印結果:

有參構造 [John, 31, 男] false //說明不是同一個對象

九、序列化對象注意事項


  • 對象的類名、屬性都會被序列化;而方法、static屬性(靜態屬性)、transient屬性(即瞬態屬性)都不會被序列化(這也就是第4條注意事項的原因)
  • 雖然加static也能讓某個屬性不被序列化,但static不是這么用的
  • 要序列化的對象的引用屬性也必須是可序列化的,否則該對象不可序列化,除非以transient關鍵字修飾該屬性使其不用序列化。
  • 反序列化地象時必須有序列化對象生成的class文件(很多沒有被序列化的數據需要從class文件獲取)
  • 當通過文件、網絡來讀取序列化后的對象時,必須按實際的寫入順序讀取。
  • 總結

    以上是生活随笔為你收集整理的Java对象序列化详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。