最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Java 對象序列化和反序列化

 更新時間:2017年03月30日 16:35:27   作者:Walker_YAM  
本篇文章主要介紹了Java 對象序列化和反序列化,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

之前的文章中我們介紹過有關(guān)字節(jié)流字符流的使用,當(dāng)時我們對于將一個對象輸出到流中的操作,使用DataOutputStream流將該對象中的每個屬性值逐個輸出到流中,讀出時相反。在我們看來這種行為實在是繁瑣,尤其是在這個對象中屬性值很多的時候?;诖?,Java中對象的序列化機制就可以很好的解決這種操作。本篇就簡單的介紹Java對象序列化,主要內(nèi)容如下:

  1. 簡潔的代碼實現(xiàn)
  2. 序列化實現(xiàn)的基本算法
  3. 兩種特殊的情況
  4. 自定義序列化機制
  5. 序列化的版本控制

一、簡潔的代碼實現(xiàn)

在介紹對象序列化的使用方法之前,先看看我們之前是怎么存儲一個對象類型的數(shù)據(jù)的。

//簡單定義一個Student類
public class Student {

 private String name;
 private int age;

 public Student(){}
 public Student(String name,int age){
 this.name = name;
 this.age=age;
 }

 public void setName(String name){
 this.name = name;
 }
 public void setAge(int age){
 this.age = age;
 }
 public String getName(){
 return this.name;
 }
 public int getAge(){
 return this.age;
 }
 //重寫toString
 @Override
 public String toString(){
 return ("my name is:"+this.name+" age is:"+this.age);
 }
}
//main方法實現(xiàn)了將對象寫入文件并讀取出來
public static void main(String[] args) throws IOException{

 DataOutputStream dot = new DataOutputStream(new FileOutputStream("hello.txt"));
 Student stuW = new Student("walker",21);
 //將此對象寫入到文件中
 dot.writeUTF(stuW.getName());
 dot.writeInt(stuW.getAge());
 dot.close();

 //將對象從文件中讀出
 DataInputStream din = new DataInputStream(new FileInputStream("hello.txt"));
 Student stuR = new Student();
 stuR.setName(din.readUTF());
 stuR.setAge(din.readInt());
 din.close();

 System.out.println(stuR);
 }

輸出結(jié)果:my name is:walker age is:21

顯然這種代碼書寫是繁瑣的,接下來我們看看,如何使用序列化來完成保存對象的信息。

public static void main(String[] args) throws IOException, ClassNotFoundException {

 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
 Student stuW = new Student("walker",21);
 oos.writeObject(stuW);
 oos.close();

 //從文件中讀取該對象返回
 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
 Student stuR = (Student)ois.readObject();
 System.out.println(stuR);
 }

寫入文件時,只用了一條語句就是writeObject,讀取時也是只用了一條語句readObject。并且Student中的那些set,get方法都用不到了。是不是很簡潔呢?接下來介紹實現(xiàn)細(xì)節(jié)。

二、實現(xiàn)序列化的基本算法

在這種機制中,每個對象都是對應(yīng)著唯一的一個序列號,而每個對象在被保存的時候也是根據(jù)這個序列號來對應(yīng)著每個不同的對象,對象序列化就是指利用了每個對象的序列號進行保存和讀取的。首先以寫對象到流中為例,對于每個對象,第一次遇到的時候會將這個對象的基本信息保存到流中,如果當(dāng)前遇到的對象已經(jīng)被保存過了,就不會再次保存這些信息,轉(zhuǎn)而記錄此對象的序列號(因為數(shù)據(jù)沒必要重復(fù)保存)。對于讀的情況,從流中遇到的每個對象,如果第一次遇到,直接輸出,如果讀取到的是某個對象的序列號,就會找到相關(guān)聯(lián)的對象,輸出。

說明幾點,一個對象要想是可序列化的,就必須實現(xiàn)接口 java.io.Serializable;,這是一個標(biāo)記接口,不用實現(xiàn)任何的方法。而我們的ObjectOutputStream流,就是一個可以將對象信息轉(zhuǎn)為字節(jié)的流,構(gòu)造函數(shù)如下:

public ObjectOutputStream(OutputStream out)

也就是所有字節(jié)流都可以作為參數(shù)傳入,兼容一切字節(jié)操作。在這個流中定義了writeObject和readObject方法,實現(xiàn)了序列化對象和反序列化對象。當(dāng)然,我們也是可以通過在類中實現(xiàn)這兩個方法來自定義序列化機制,具體的后文介紹。此處我們只需要了解整個序列化機制,所有的對象數(shù)據(jù)只會保存一份,至于相同的對象再次出現(xiàn),只保存對應(yīng)的序列號。下面,通過兩個特殊的情況直觀的感受下他的這個基本算法。

三、兩個特殊的實例

先看第一個實例:

public class Student implements Serializable {

 String name;
 int age;
 Teacher t; //另外一個對象類型

 public Student(){}
 public Student(String name,int age,Teacher t){
 this.name = name;
 this.age=age;
 this.t = t;
 }

 public void setName(String name){this.name = name;}
 public void setAge(int age){this.age = age;}
 public void setT(Teacher t){this.t = t;}
 public String getName(){return this.name;}
 public int getAge(){return this.age;}
 public Teacher getT(){return this.t;}
}

public class Teacher implements Serializable {
 String name;

 public Teacher(String name){
 this.name = name;
 }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

 Teacher t = new Teacher("li");
 Student stu1 = new Student("walker",21,t);
 Student stu2 = new Student("yam",22,t);
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
 oos.writeObject(stu1);
 oos.writeObject(stu2);

 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
 Student stuR1 = (Student)ois.readObject();
 Student stuR2 = (Student)ois.readObject();

 if (stuR1.getT() == stuR2.getT())
  System.out.println("相同對象");
 }

結(jié)果是很顯而易見的,輸出了相同對象。我們在main函數(shù)中定義了兩個student類型對象,他們卻都引用的同一個teacher對象在內(nèi)部。完成序列化之后,反序列化出來兩個對象,通過比較他們內(nèi)部的teacher對象是否是同一個實例,可以看出來,在序列化第一個student對象的時候t是被寫入流中的,但是在遇到第二個student對象的teacher對象實例時,發(fā)現(xiàn)前面已經(jīng)寫過了,于是不再寫入流中,只保存對應(yīng)的序列號作為引用。當(dāng)然在反序列化的時候,原理類似。這和我們上面介紹的基本算法是一樣的。

下面看第二個特殊實例:

public class Student implements Serializable {

 String name;
 Teacher t;

}

public class Teacher implements Serializable {
 String name;
 Student stu;

}

public static void main(String[] args) throws IOException, ClassNotFoundException {

 Teacher t = new Teacher();
 Student s =new Student();
 t.name = "walker";
 t.stu = s;
 s.name = "yam";
 s.t = t;

 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
 oos.writeObject(t);
 oos.writeObject(s);
 oos.close();

 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
 Teacher tR = (Teacher)ois.readObject();
 Student sR = (Student)ois.readObject();
 if(tR == sR.t && sR == tR.stu)System.out.println("ok");

 }

輸出的結(jié)果是ok,這個例子可以叫做:循環(huán)引用。從結(jié)果我們可以看出來,序列化之前兩個對象存在的相互的引用關(guān)系,經(jīng)過序列化之后,兩者之間的這種引用關(guān)系是依然存在的。其實按照我們之前介紹的判斷算法來看,首先我們先序列化了teacher對象,因為他內(nèi)部引用了student的對象,兩者都是第一次遇到,所以將兩者序列化到流中,然后我們?nèi)バ蛄谢痵tudent對象,發(fā)現(xiàn)這個對象以及內(nèi)部的teacher對象都已經(jīng)被序列化了,于是只保存對應(yīng)的序列號。讀取的時候根據(jù)序列號恢復(fù)對象。

四、自定義序列化機制

綜上,我們已經(jīng)介紹完了基本的序列化與反序列化的知識。但是往往我們會有一些特殊的要求,這種默認(rèn)的序列化機制雖然已經(jīng)很完善了,但是有些時候還是不能滿足我們的需求。所以我們看看如何自定義序列化機制。自定義序列化機制中,我們會使用到一個關(guān)鍵字,它也是我們之前在看源碼的時候經(jīng)常遇到的,transient。將字段聲明transient,等于是告訴默認(rèn)的序列化機制,這個字段你不要給我寫到流中去,我會自己處理的。

public class Student implements Serializable {

 String name;
 transient int age;

 public String toString(){
 return this.name + ":" + this.age;
 }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
 Student stu = new Student();
 stu.name = "walker";stu.age = 21;
 oos.writeObject(stu);

 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
 Student stuR = (Student)ois.readObject();

 System.out.println(stuR);
 }

輸出結(jié)果:walker:0

我們不是給age字段賦初始值了么,怎么會是0呢?正如我們上文所說的一樣,被transient修飾的字段不會被寫入流中,自然讀取出來就沒有值,默認(rèn)是0。下面看看我們怎么自己來序列化這個age。

//改動過的student類,main方法沒有改動,大家可以往上看
public class Student implements Serializable {

 String name;
 transient int age;

 private void writeObject(ObjectOutputStream oos) throws IOException {
 oos.defaultWriteObject();

 oos.writeInt(25);
 }

 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
 ois.defaultReadObject();

 age = ois.readInt();
 }

 public String toString(){
 return this.name + ":" + this.age;
 }
}

輸出結(jié)果:walker:25

結(jié)果既不是我么初始化的21,也不是0,而是我們在writeObject方法中寫的25。現(xiàn)在我們一點一點看看每個步驟的意義。首先,要想要實現(xiàn)自定義序列化,就需要在該對象定義的類中實現(xiàn)兩個方法,writeObject和readObject,而且格式必須和上面貼出來的一樣,筆者試過改動方法修飾符,結(jié)果導(dǎo)致不能成功序列化。這是因為,Java采用反射機制,檢查該對象所在的類中有沒有實現(xiàn)這兩個方法,沒有的話就使用默認(rèn)的ObjectOutputStream中的這個方法序列化所有字段,如果有的話就執(zhí)行你自己實現(xiàn)的這個方法。

接下來,看看這兩個方法實現(xiàn)的細(xì)節(jié),先看writeObject方法,參數(shù)是ObjectOutputStream 類型的,這個拿到的是我們在main方法中定義的ObjectOutputStream 對象,要不然它怎么知道該把對象寫到那個地方去呢?第一行我們調(diào)用的是oos.defaultWriteObject();這個方法實現(xiàn)的功能是,將當(dāng)前對象中所有沒有被transient修飾的字段寫入流中,第二條語句我們顯式的調(diào)用了writeInt方法將age的值寫入流中。讀取的方法類似,此處不再贅述。

五、版本控制

最后我們來看看,序列化過程的的版本控制問題。在我們將一個對象序列化到流中之后,該對象對應(yīng)的類的結(jié)構(gòu)改變了,如果此時我們再次從流中將之前保存的對象讀取出來,會發(fā)生什么?這要分情況來說,如果原類中的字段被刪除了,那從流中輸出的對應(yīng)的字段將會被忽略。如果原類中增加了某個字段,那新增的字段的值就是默認(rèn)值。如果字段的類型發(fā)生了改變,拋出異常。在Java中每個類都會有一個記錄版本號的變量:static final serivalVersionUID = 115616165165L,此處的值只用于演示并不對應(yīng)任意某個類。這個版本號是根據(jù)該類中的字段等一些屬性信息計算出來的,唯一性較高。每次讀出的時候都會去比較之前和現(xiàn)在的版本號確認(rèn)是否發(fā)生版本不一致情況,如果版本不一致,就會按照上述的情形分別做處理。

對象的序列化就寫完了,如果有什么內(nèi)容不妥的地方,希望大家指出!

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 使用IDEA對SpringBoot應(yīng)用進行遠(yuǎn)程調(diào)試方式

    使用IDEA對SpringBoot應(yīng)用進行遠(yuǎn)程調(diào)試方式

    文章介紹了如何在IDEA中對部署在服務(wù)器上的SpringBoot應(yīng)用進行遠(yuǎn)程調(diào)試,通過配置遠(yuǎn)程調(diào)試端口和啟動參數(shù),本地IDEA可以設(shè)置斷點并進行調(diào)試
    2025-02-02
  • springboottest測試依賴和使用方式

    springboottest測試依賴和使用方式

    這篇文章主要介紹了springboottest測試依賴和使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java實現(xiàn)抖音代碼舞源碼

    java實現(xiàn)抖音代碼舞源碼

    這篇文章主要為大家詳細(xì)介紹了java實現(xiàn)抖音代碼舞的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-04-04
  • Java try catch finally異常處理組合詳解

    Java try catch finally異常處理組合詳解

    這篇文章主要介紹了Java try catch finally異常處理組合詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • Java中將List拆分為多個小list集合的實現(xiàn)代碼

    Java中將List拆分為多個小list集合的實現(xiàn)代碼

    這篇文章主要介紹了Java中如何將List拆分為多個小list集合,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • Java高級架構(gòu)之FastDFS分布式文件集群詳解

    Java高級架構(gòu)之FastDFS分布式文件集群詳解

    這篇文章主要介紹了Java高級架構(gòu)之FastDFS分布式文件集群詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-04-04
  • Spring事務(wù)處理流程和原理詳解

    Spring事務(wù)處理流程和原理詳解

    這篇文章主要介紹了Spring事務(wù)處理流程和原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • SpringMVC框架實現(xiàn)Handler處理器的三種寫法

    SpringMVC框架實現(xiàn)Handler處理器的三種寫法

    這篇文章主要介紹了SpringMVC框架實現(xiàn)Handler處理器的三種寫法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • Spring Boot配置線程池拒絕策略的場景分析(妥善處理好溢出的任務(wù))

    Spring Boot配置線程池拒絕策略的場景分析(妥善處理好溢出的任務(wù))

    本文通過實例代碼給大家介紹下如何為線程池配置拒絕策略、如何自定義拒絕策略。對Spring Boot配置線程池拒絕策略的相關(guān)知識感興趣的朋友一起看看吧
    2021-09-09
  • 詳解Java ArrayList類

    詳解Java ArrayList類

    這篇文章主要介紹了Java ArrayList類的相關(guān)資料,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07

最新評論

上高县| 康定县| 密云县| 普洱| 云霄县| 阜康市| 五台县| 宁德市| 惠水县| 库尔勒市| 东宁县| 龙州县| 延津县| 三门县| 新津县| 若尔盖县| 丹东市| 扎鲁特旗| 赞皇县| 阿克苏市| 千阳县| 邻水| 弋阳县| 昌图县| 囊谦县| 南部县| 威海市| 孟州市| 南昌县| 农安县| 合肥市| 濮阳市| 防城港市| 凤翔县| 读书| 仁化县| 怀宁县| 定日县| 新巴尔虎右旗| 安岳县| 湘乡市|