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

歡迎訪問 生活随笔!

生活随笔

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

java

Java序列化,看这篇就够了!

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

一、序列化的含義、意義及使用場景

  • 序列化:將對象寫入到IO流中

  • 反序列化:從IO流中恢復對象

  • 意義:序列化機制允許將實現序列化的Java對象轉換位字節序列,這些字節序列可以保存在磁盤上,或通過網絡傳輸,以達到以后恢復成原來的對象。序列化機制使得對象可以脫離程序的運行而獨立存在。

  • 使用場景:所有可在網絡上傳輸的對象都必須是可序列化的,比如RMI(remote method invoke,即遠程方法調用),傳入的參數或返回的對象都是可序列化的,否則會出錯;所有需要保存到磁盤的java對象都必須是可序列化的。通常建議:程序創建的每個JavaBean類都實現Serializeable接口。

二、序列化實現的方式

如果需要將某個對象保存到磁盤上或者通過網絡傳輸,那么這個類應該實現Serializable接口或者Externalizable接口之一。

1、Serializable

1.1 普通序列化

Serializable接口是一個標記接口,不用實現任何方法。一旦實現了此接口,該類的對象就是可序列化的。

  • 序列化步驟:

    • 步驟一:創建一個ObjectOutputStream輸出流;

    • 步驟二:調用ObjectOutputStream對象的writeObject輸出可序列化對象。

    public class Person implements Serializable {

    private String name;

    private int age;

    //我不提供無參構造器

    public Person(String name, int age) {

    this.name = name;

    this.age = age;

    }

    @Override

    public String toString() {

    return "Person{" +

    "name='" + name + ''' +

    ", age=" + age +

    '}';

    }

    }

    pblic class WriteObject {

    public static void main(String[] args) {

    try (//創建一個ObjectOutputStream輸出流

    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))) {

    //將對象序列化到文件s

    Person person = new Person("9龍", 23);

    oos.writeObject(person);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    }

  • 反序列化步驟:

    • 步驟一:創建一個ObjectInputStream輸入流;

    • 步驟:調用ObjectInputStream對象的readObject()得到序列化的對象。

    • 我們將上面序列化到person.txt的person對象反序列化回來

    public?class?Person?implements?Serializable?{private?String?name;private?int?age;//我不提供無參構造器public?Person(String?name,?int?age)?{System.out.println("反序列化,你調用我了嗎?");this.name?=?name;this.age?=?age;}@Oerridepublic?String?toString()?{return?"Person{"?+"name='"?+?name?+?'''?+",?age="?+?age?+'}';} } publc?class?ReadObject?{public?static?void?main(String[]?args)?{try?(//創建一個ObjectInputStream輸入流ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream("person.txt")))?{Person?brady?=?(Person)?ois.readObject();System.out.println(brady);}?catch?(Exception?e)?{e.printStackTrace();}} } //輸出結果 //Person{name='9龍',?age=23}
    • waht???? 輸出告訴我們,反序列化并不會調用構造方法。反序列的對象是由JVM自己生成的對象,不通過構造方法生成。

    1.2 成員是引用的序列化

    如果一個可序列化的類的成員不是基本類型,也不是String類型,那這個引用類型也必須是可序列化的;否則,會導致此類不能序列化。

    看例子,我們新增一個Teacher類。將Person去掉實現Serializable接口代碼。

    public?class?Person{//省略相關屬性與方法 } public?class?Teacher?implements?Serializable?{pivate?String?name;private?Person?person;pubic?Teacher(String?name,?Person?person)?{this.name?=?name;this.person?=?person;}pulic?static?void?main(String[]?args)?throws?Exception?{try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("teacher.txt")))?{Person?person?=?new?Person("路飛",?20);Teacher?teacher?=?new?Teacher("雷利",?person);oos.writeObject(teacher);}} }


    我們看到程序直

    錯,因為Person類的對象是不可序列化的,這導致了Teacher的對象不可序列化

    1.3 同一對象序列化多次的機制

    同一對象序列化多次,會將這個對象序列化多次嗎?答案是否定的。

    public?class?WriteTeacher?{public?static?void?main(String[]?args)?throws?Exception?{try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("teacher.txt")))?{Person?person?=?new?Person("路飛",?20);Teacher?t1?=?new?Teacher("雷利",?person);Teacher?t2?=?new?Teacher("紅發香克斯",?person);//依次將4個對象寫入輸入流oos.writeObject(t1);oos.writeObject(t2);oos.writeObject(person);oos.writeObject(t2);}} }

    依次將t1、t2、person、t2對象序列化到文件teacher.txt文件中。

    注意:反序列化的順序與序列化時的順序一致。

    public?class?ReadTeacher?{public?static?void?main(String[]?args)?{try?(ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream("teacher.txt")))?{Teacher?t1?=?(Teacher)?ois.readObject();Teacher?t2?=?(Teacher)?ois.readObject();Person?p?=?(Person)?ois.readObject();Teacher?t3?=?(Teacher)?ois.readObject();System.out.println(t1?==?t2);System.out.println(t1.getPerson()?==?p);System.out.println(t2.getPerson()?==?p);System.out.println(t2?==?t3);System.out.println(t1.getPerson()?==?t2.getPerson());}?catch?(Exception?e)?{e.printStackTrace();}} } //輸出結果 //false //true //true //true //true

    從輸出結果可以看出,Java序列化同一對象,并不會將此對象序列化多次得到多個對象。

    • Java序列化算法

  • 所有保存到磁盤的對象都有一個序列化編碼號

  • 當程序試圖序列化一個象時,會先檢查此對象是否已經序列化過,只有此對象從未(在此虛擬機)被序列化過,才會將此對象序列化為字節序列輸出。

  • 如果此對象已經序列化過則直接輸出編號即可。

  • 圖示上述序列化過程。


  • 1.4 java序列

    法潛在的問題

    由于java序利化算法不會重復序列化同一個對象,只會記錄已序列化對象的編號。如果序列化一個可變對象(對象內的內容可更改)后,更改了對象內容,再次序列化,并不會再次將此對象轉換為字節序列,而只是保存序列化編號。

    public?class?WriteObject?{public?static?void?main(String[]?args)?{try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("person.txt"));ObjectInputStream?ios?=?new?ObjectInputStream(new?FileInputStream("person.txt")))?{//第一次序列化personPerson?person?=?new?Person("9龍",?23);oos.writeObject(person);System.out.println(person);/修改nameperson.setName("海賊王");System.out.println(person);//第二次序列化personoos.writeObject(person);//次反序列化出p1、p2Person?p1?=?(Person)?ios.readObject();Person?p2?=?(Person)?ios.readObject();System.out.println(p1?==?p2);System.out.println(p1.getName().equals(p2.getName()));}?catch?(Exception?e)?{e.printStackTrace();}} } //輸出結果 //Person{name='9龍',?age=23} //Person{name='海賊王',?age=23} //true //true

    1.5 可選的自定義序列化

  • 有些時候,我們有這樣的需求,某些屬性不需要序列化。使用transient關鍵字選擇不需要序列化的字段。

  • public?class?Person?implements?Serializable?{//不需要序列化名字與年齡private?transient?String?name;private?transient?int?age;private?int?height;private?transient?boolean?singlehood;public?Person(String?name,?int?age)?{this.name?=?name;this.age?=?age;}//省略get,set方法 } public?class?TansientTest?{public?static?void?main(String[]?args)?throws?Exception?{try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("person.txt"));ObjectInputStream?ios?=?new?ObjectInputStream(new?FileInputStream("person.txt")))?{Person?person?=?new?Person("9龍",?23);person.setHeight(185);System.out.println(person);oos.writeObject(person);Person?p1?=?(Person)ios.readObject();System.out.println(p1);}} } //輸出結果 //Person{name='9龍',?age=23',?singlehood=true',?height=185cm} //Person{name='null',?age=0',?singlehood=false',?height=185cm}
  • 從輸出我們看到,使用transient修飾的屬性,java序列化時,會忽略掉此字段,所以反序列化出的對象,被transient修飾的屬性是默認值。對于引用類型,值是null;基本類型,值是0;boolean類型,值是false。

  • 使用transient雖然簡單,但將此屬性完全隔離在了序列化之外。java提供了可選的自定義序列化。可以進行控制序列化的方式,或者對序列化數據進行編碼加密等。

  • private?void?writeObject(java.io.ObjectOutputStream?out)?throws?IOException; private?void?readObject(java.io.ObjectIutputStream?in)?throws?IOException,ClassNotFoundException; private?void?readObjectNoData()?throws?ObjectStreamException;
  • 通過重寫writeObject與readObject方法,可以自己選擇哪些屬性需要序列化, 哪些屬性不需要。如果writeObject使用某種規則序列化,則相應的readObject需要相反的規則反序列化,以便能正確反序列化出對象。這里展示對名字進行反轉加密。

  • public?class?Person?implements?Serializable?{private?String?name;private?int?age;//省略構造方法,get及set方法private?void?riteObject(ObjectOutputStream?out)?throws?IOException?{//將名字反轉寫入二進制流out.writeObject(new?StringBuffer(this.name).reverse());out.writeInt(age);}private?void?eadObject(ObjectInputStream?ins)?throws?IOException,ClassNotFoundException{//將讀出的字符串反轉恢復回來this.name?=?((StringBuffer)ins.readObject()).reverse().toString();this.age?=?ins.readInt();} }
  • 當序列化流不完整時,readObjectNoData()方法可以用來正確地初始化反序列化的對象。例如,使用不同類接收反序列化對象,或者序列化流被篡改時,系統都會調用readObjectNoData()方法來初始化反序列化的對象。

  • 更徹底的自定義序列化

  • ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

  • ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

    • writeReplace:在序列化時,會先調用此方法,再調用writeObject方法。此方法可將任意對象代替目標序列化對象

    public?class?Person?implements?Serializable?{private?String?name;private?int?age;//省略構造方法,get及set方法private?Object?witeReplace()?throws?ObjectStreamException?{ArrayList<Object>?list?=?new?ArrayList<>(2);list.add(this.name);list.add(this.age);return?list;}public?static?vid?main(String[]?args)?throws?Exception?{try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("person.txt"));ObjectInputStream?ios?=?new?ObjectInputStream(new?FileInputStream("person.txt")))?{Person?person?=?new?Person("9龍",?23);oos.writeObject(person);ArrayList?list?=?(ArrayList)ios.readObject();System.out.println(list);}} } //輸出結果 //[9龍,?23]
    • readResolve:反序列化時替換反序列化出的對象,反序列化出來的對象被立即丟棄。此方法在readeObject后調用。

    public?class?Person?implements?Serializable?{private?String?name;private?int?age;//省略構造方法,get及set方法private?Object?readResolve()?throws?ObjectStreamException{return?new?("brady",?23);}public?static?void?main(String[]?args)?throws?Exception?{try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("person.txt"));ObjectInputStream?ios?=?new?ObjectInputStream(new?FileInputStream("person.txt")))?{Person?person?=?new?Person("9龍",?23);oos.writeObject(person);HashMap?map?=?(HashMap)ios.readObject();System.out.println(map);}} } //輸出結果 //{brady=23}
    • readResolve常用來反序列單例類,保證單例類的唯一性。

    • 注意:readResolve與writeReplace的訪問修飾符可以是private、protected、public,如果父類重寫了這兩個方法,子類都需要根據自身需求重寫,這顯然不是一個好的設計。通常建議對于final修飾的類重寫readResolve方法沒有問題;否則,重寫readResolve使用private修飾。

    2、Externalizable:強制自定義序列化

    通過實現Externalizable接口,必須實現writeExternal、readExternal方法。

    public?interface?Externalizable?extends?java.io.Serializable?{void?writeExternal(ObjectOutput?out)?throws?IOException;void?readExternal(ObjectInput?in)?throws?IOException,?ClassNotFoundException; } 復制代碼 public?class?ExPerson?implements?Externalizable?{private?String?ame;private?int?age;//注意,必須加上pulic?無參構造器public?ExPerson()?{}public?ExPerson(Sring?name,?int?age)?{this.name?=?name;this.age?=?age;}@Overridepubic?void?writeExternal(ObjectOutput?out)?throws?IOException?{//將name反轉后寫入二進制流StringBuffer?reverse?=?new?StringBuffer(name).reverse();System.out.println(reverse.toString());out.writeObject(reverse);out.writeInt(age);}@Overridepubli?void?readExternal(ObjectInput?in)?throws?IOException,?ClassNotFoundException?{//將讀取的字符串反轉后賦值給name實例變量this.name?=?((StringBuffer)?in.readObject()).reverse().toString();System.out.println(name);this.age?=?in.readInt();}public?static?void?ain(String[]?args)?throws?IOException,?ClassNotFoundException?{try?(ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream("ExPerson.txt"));ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream("ExPerson.txt")))?{oos.writeObject(new?ExPerson("brady",?23));ExPerson?ep?=?(ExPerson)?ois.readObject();System.out.println(ep);}} } //輸出結果 //ydarb //brady //ExPerson{name='brady',?age=23}

    注意:Externalizable接口不同于Serializable接口,實現此接口必須實現接口中的兩個方法實現自定義序列化,這是強制性的;特別之處是必須提供pulic的無參構造器,因為在反序列化的時候需要反射創建對象。

    3、兩種序列化對比

    實現Serializable接口實現Externalizable接口系統自動存儲必要的信息程序員決定存儲哪些信息Java內建支持,易于實現,只需要實現該接口即可,無需任何代碼支持必須實現接口內的兩個方法性能略差性能略好

    雖然Externalizable接口帶來了一定的性能提升,但變成復雜度也提高了,所以一般通過實現Serializable接口進行序列化。

    三、序列化版本號serialVersionUID

    我們知道,反序列化必須擁有class文件,但隨著項目的升級,class文件也會升級,序列化怎么保證升級前后的兼容性呢?

    java序列化提供了一個private static final long serialVersionUID 的序列化版本號,只有版本號相同,即使更改了序列化屬性,對象也可以正確被反序列化回來。

    public?class?Person?implements?Serializable?{//序列化版本號private?static?final?long?serialVersionUID?=?1111013L;private?String?name;private?int?age;//省略構造方法及get,set }

    如果反序列化使用的class的版本號與序列化時使用的不一致,反序列化會報InvalidClassException異常。


    序列化版本號可自由指定,如果不指定,JVM會根

    息自己計算一個版本號,這樣隨著class的升級,就無法正確反序列化;不指定版本號另一個明顯隱患是,不利于jvm間的移植,可能class文件沒有更改,但不同jvm可能計算的規則不一樣,這樣也會導致無法反序列化。

    什么情況下需要修改serialVersionUID呢?分三種情況。

    • 如果只是修改了方法,反序列化不容影響,則無需修改版本號;

    • 如果只是修改了靜態變量,瞬態變量(transient修飾的變量),反序列化不受影響,無需修改版本號;

    • 如果修改了非瞬態變量,則可能導致反序列化失敗。如果新類中實例變量的類型與序列化時類的類型不一致,則會反序列化失敗,這時候需要更改serialVersionUID。如果只是新增了實例變量,則反序列化回來新增的是默認值;如果減少了實例變量,反序列化時會忽略掉減少的實例變量。

    四、總結

  • 所有需要網絡傳輸的對象都需要實現序列化接口,通過建議所有的javaBean都實現Serializable接口。

  • 對象的類名、實例變量(包括基本類型,數組,對其他對象的引用)都會被序列化;方法、類變量、transient實例變量都不會被序列化。

  • 如果想讓某個變量不被序列化,使用transient修飾。

  • 序列化對象的引用類型成員變量,也必須是可序列化的,否則,會報錯。

  • 反序列化時必須有序列化對象的class文件。

  • 當通過文件、網絡來讀取序列化后的對象時,必須按照實際寫入的順序讀取。

  • 單例類序列化,需要重寫readResolve()方法;否則會破壞單例原則。

  • 同一對象序列化多次,只有第一次序列化為二進制流,以后都只是保存序列化編號,不會重復序列化。

  • 建議所有可序列化的類加上serialVersionUID 版本號,方便項目升級



  • 更多資料展示如下,需要的小伙伴歡迎加群:Java架構度寒冬,962992162


    轉載于:https://blog.51cto.com/14378044/2406615

    總結

    以上是生活随笔為你收集整理的Java序列化,看这篇就够了!的全部內容,希望文章能夠幫你解決所遇到的問題。

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