溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Java 中對(duì)象的序列化和反序列化有什么區(qū)別

發(fā)布時(shí)間:2020-11-12 16:28:00 來(lái)源:億速云 閱讀:166 作者:Leah 欄目:編程語(yǔ)言

本篇文章給大家分享的是有關(guān)Java 中對(duì)象的序列化和反序列化有什么區(qū)別,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話(huà)不多說(shuō),跟著小編一起來(lái)看看吧。

主要內(nèi)容如下:

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

一、簡(jiǎn)潔的代碼實(shí)現(xiàn)

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

//簡(jiǎn)單定義一個(gè)Student類(lèi)
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;
 }
 //重寫(xiě)toString
 @Override
 public String toString(){
 return ("my name is:"+this.name+" age is:"+this.age);
 }
}
//main方法實(shí)現(xiàn)了將對(duì)象寫(xiě)入文件并讀取出來(lái)
public static void main(String[] args) throws IOException{

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

 //將對(duì)象從文件中讀出
 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

顯然這種代碼書(shū)寫(xiě)是繁瑣的,接下來(lái)我們看看,如何使用序列化來(lái)完成保存對(duì)象的信息。

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();

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

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

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

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

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

public ObjectOutputStream(OutputStream out)

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

三、兩個(gè)特殊的實(shí)例

先看第一個(gè)實(shí)例:

public class Student implements Serializable {

 String name;
 int age;
 Teacher t; //另外一個(gè)對(duì)象類(lèi)型

 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("相同對(duì)象");
 }

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

下面看第二個(gè)特殊實(shí)例:

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

四、自定義序列化機(jī)制

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

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字段賦初始值了么,怎么會(huì)是0呢?正如我們上文所說(shuō)的一樣,被transient修飾的字段不會(huì)被寫(xiě)入流中,自然讀取出來(lái)就沒(méi)有值,默認(rèn)是0。下面看看我們?cè)趺醋约簛?lái)序列化這個(gè)age。

//改動(dòng)過(guò)的student類(lèi),main方法沒(méi)有改動(dòng),大家可以往上看
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,而是我們?cè)趙riteObject方法中寫(xiě)的25?,F(xiàn)在我們一點(diǎn)一點(diǎn)看看每個(gè)步驟的意義。首先,要想要實(shí)現(xiàn)自定義序列化,就需要在該對(duì)象定義的類(lèi)中實(shí)現(xiàn)兩個(gè)方法,writeObject和readObject,而且格式必須和上面貼出來(lái)的一樣,筆者試過(guò)改動(dòng)方法修飾符,結(jié)果導(dǎo)致不能成功序列化。這是因?yàn)椋琂ava采用反射機(jī)制,檢查該對(duì)象所在的類(lèi)中有沒(méi)有實(shí)現(xiàn)這兩個(gè)方法,沒(méi)有的話(huà)就使用默認(rèn)的ObjectOutputStream中的這個(gè)方法序列化所有字段,如果有的話(huà)就執(zhí)行你自己實(shí)現(xiàn)的這個(gè)方法。

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

五、版本控制

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

以上就是Java 中對(duì)象的序列化和反序列化有什么區(qū)別,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI