Java序列化與反序列化

      網(wǎng)友投稿 838 2025-03-31

      一、 序列化和反序列化概念

      Serialization(序列化)是一種將對(duì)象以一連串的字節(jié)描述的過(guò)程;反序列化deserialization是一種將這些字節(jié)重建成一個(gè)對(duì)象的過(guò)程。將程序中的對(duì)象,放入文件中保存就是序列化,將文件中的字節(jié)碼重新轉(zhuǎn)成對(duì)象就是反序列化。

      二、 序列化和反序列化的必要性

      當(dāng)兩個(gè)進(jìn)程進(jìn)行遠(yuǎn)程通信時(shí),可以相互發(fā)送各種類型的數(shù)據(jù),包括文本、圖片、音頻、視頻等, 而這些數(shù)據(jù)都會(huì)以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送。

      而java是面向?qū)ο蟮拈_發(fā)方式,一切都是java對(duì)象,想要實(shí)現(xiàn)java對(duì)象的網(wǎng)絡(luò)傳輸,就可以使用序列化和反序列化來(lái)實(shí)現(xiàn)。發(fā)送方將需要發(fā)送的Java對(duì)象序列化轉(zhuǎn)換為字節(jié)序列,然后在網(wǎng)絡(luò)上傳送;接收方接收到字符序列后,使用反序列化從字節(jié)序列中恢復(fù)出Java對(duì)象。

      當(dāng)我們了解了為什么需要Java序列化和反序列化后,我們很自然地會(huì)想Java序列化的好處。其好處一是實(shí)現(xiàn)了數(shù)據(jù)的持久化,通過(guò)序列化可以把數(shù)據(jù)永久地保存到硬盤上(通常存放在文件里);二是,利用序列化實(shí)現(xiàn)遠(yuǎn)程通信,即在網(wǎng)絡(luò)上傳送對(duì)象的字節(jié)序列。

      總結(jié),在網(wǎng)絡(luò)中數(shù)據(jù)的傳輸必須是序列化形式來(lái)進(jìn)行的。其他序列化的方式可以是json傳輸,xml形式傳輸。

      三、 序列化和反序列化的實(shí)現(xiàn)

      java.io.ObjectOutputStream:表示對(duì)象輸出流

      它的writeObject(Object obj)方法可以對(duì)參數(shù)指定的obj對(duì)象進(jìn)行序列化,把得到的字節(jié)序列寫到一個(gè)目標(biāo)輸出流中。

      java.io.ObjectInputStream:表示對(duì)象輸入流

      它的readObject()方法從源輸入流中讀取字節(jié)序列,再把它們反序列化成為一個(gè)對(duì)象,并將其返回。

      只有實(shí)現(xiàn)了Serializable或Externalizable接口的類的對(duì)象才能被序列化,否則拋出異常。

      假定一個(gè)Student類,它的對(duì)象需要序列化,可以有如下三種方法:

      方法一:若Student類僅僅實(shí)現(xiàn)了Serializable接口,則可以按照以下方式進(jìn)行序列化和反序列化。

      ObjectOutputStream采用默認(rèn)的序列化方式,對(duì)Student對(duì)象的非transient的實(shí)例變量進(jìn)行序列化。

      ObjcetInputStream采用默認(rèn)的反序列化方式,對(duì)對(duì)Student對(duì)象的非transient的實(shí)例變量進(jìn)行反序列化。

      方法二:若Student類僅僅實(shí)現(xiàn)了Serializable接口,并且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則采用以下方式進(jìn)行序列化與反序列化。

      ObjectOutputStream調(diào)用Student對(duì)象的writeObject(ObjectOutputStream out)的方法進(jìn)行序列化。

      ObjectInputStream會(huì)調(diào)用Student對(duì)象的readObject(ObjectInputStream in)的方法進(jìn)行反序列化。

      方法三:若Student類實(shí)現(xiàn)了Externalnalizable接口,且Student類必須實(shí)現(xiàn)readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進(jìn)行序列化與反序列化。

      ObjectOutputStream調(diào)用Student對(duì)象的writeExternal(ObjectOutput out))的方法進(jìn)行序列化。

      ObjectInputStream會(huì)調(diào)用Student對(duì)象的readExternal(ObjectInput in)的方法進(jìn)行反序列化。

      步驟一:創(chuàng)建一個(gè)對(duì)象輸出流,它可以包裝一個(gè)其它類型的目標(biāo)輸出流,如文件輸出流:

      ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\objectfile.obj”));

      步驟二:通過(guò)對(duì)象輸出流的writeObject()方法寫對(duì)象:

      out.writeObject(“Hello”);

      out.writeObject(new Date());

      步驟一:創(chuàng)建一個(gè)對(duì)象輸入流,它可以包裝一個(gè)其它類型輸入流,如文件輸入流:

      ObjectInputStream in = new ObjectInputStream(new fileInputStream(“D:\objectfile.obj”));

      步驟二:通過(guò)對(duì)象輸出流的readObject()方法讀取對(duì)象:

      String obj1 = (String)in.readObject();

      Date obj2 = (Date)in.readObject();

      說(shuō)明:為了正確讀取數(shù)據(jù),完成反序列化,必須保證向?qū)ο筝敵隽鲗憣?duì)象的順序與從對(duì)象輸入流中讀對(duì)象的順序一致。

      為了更好地理解Java序列化與反序列化,選擇方法一編碼實(shí)現(xiàn)。

      Student類定義如下:

      /**

      * 實(shí)現(xiàn)了序列化接口的學(xué)生類

      */

      public class Student implements Serializable {

      private String name;

      private char sex;

      private int year;

      private double gpa;

      public Student() {

      }

      public Student(String name,char sex,int year,double gpa) {

      this.name = name;

      this.sex = sex;

      this.year = year;

      this.gpa = gpa;

      }

      public void setName(String name) {

      this.name = name;

      }

      public void setSex(char sex) {

      this.sex = sex;

      }

      public void setYear(int year) {

      this.year = year;

      }

      public void setGpa(double gpa) {

      this.gpa = gpa;

      }

      public String getName() {

      return this.name;

      }

      public char getSex() {

      return this.sex;

      }

      public int getYear() {

      return this.year;

      }

      public double getGpa() {

      return this.gpa;

      }

      }

      把Student類的對(duì)象序列化到文件/Users/sschen/Documents/student.txt,并從該文件中反序列化,向console顯示結(jié)果。代碼如下:

      public class UserStudent {

      public static void main(String[] args) {

      Student st = new Student("Tom",'M',20,3.6);

      File file = new File("/Users/sschen/Documents/student.txt");

      try {

      file.createNewFile();

      Java 中序列化與反序列化

      }

      catch(IOException e) {

      e.printStackTrace();

      }

      try {

      //Student對(duì)象序列化過(guò)程

      FileOutputStream fos = new FileOutputStream(file);

      ObjectOutputStream oos = new ObjectOutputStream(fos);

      oos.writeObject(st);

      oos.flush();

      oos.close();

      fos.close();

      //Student對(duì)象反序列化過(guò)程

      FileInputStream fis = new FileInputStream(file);

      ObjectInputStream ois = new ObjectInputStream(fis);

      Student st1 = (Student) ois.readObject();

      System.out.println("name = " + st1.getName());

      System.out.println("sex = " + st1.getSex());

      System.out.println("year = " + st1.getYear());

      System.out.println("gpa = " + st1.getGpa());

      ois.close();

      fis.close();

      }

      catch(ClassNotFoundException e) {

      e.printStackTrace();

      }

      catch (IOException e) {

      e.printStackTrace();

      }

      }

      }

      而查看文件/Users/sschen/Documents/student.txt,其內(nèi)保存的內(nèi)容并不是可以容易閱讀的內(nèi)容:

      aced 0005 7372 001f 636f 6d2e 7373 6368

      656e 2e53 6572 6961 6c69 7a61 626c 652e

      5374 7564 656e 74f1 5dbd a4a0 3472 4d02

      0004 4400 0367 7061 4300 0373 6578 4900

      0479 6561 724c 0004 6e61 6d65 7400 124c

      6a61 7661 2f6c 616e 672f 5374 7269 6e67

      3b78 7040 0ccc cccc cccc cd00 4d00 0000

      1474 0003 546f 6d

      四、序列化的必要條件

      1、必須是同包,同名。

      2、serialVersionUID必須一致。有時(shí)候兩個(gè)類的屬性稍微不一致的時(shí)候,可以通過(guò)將此屬性寫死值,實(shí)現(xiàn)序列化和反序列化。

      五、序列化高級(jí),使用情境分析

      情境:兩個(gè)客戶端 A 和 B 試圖通過(guò)網(wǎng)絡(luò)傳遞對(duì)象數(shù)據(jù),A 端將對(duì)象 C 序列化為二進(jìn)制數(shù)據(jù)再傳給 B,B 反序列化得到 C。

      問(wèn)題:C 對(duì)象的全類路徑假設(shè)為 com.inout.Test,在 A 和 B 端都有這么一個(gè)類文件,功能代碼完全一致。也都實(shí)現(xiàn)了 Serializable 接口,但是反序列化時(shí)總是提示不成功。

      解決:虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。下面的代碼中,雖然兩個(gè)類的功能代碼完全一致,但是序列化 ID 不同,他們無(wú)法相互序列化和反序列化。

      簡(jiǎn)單來(lái)說(shuō),Java的序列化機(jī)制是通過(guò)在運(yùn)行時(shí)判斷類的serialVersionUID來(lái)驗(yàn)證版本一致性的。在進(jìn)行反序列化時(shí),JVM會(huì)把傳來(lái)的字節(jié)流中的serialVersionUID與本地相應(yīng)實(shí)體(類)的serialVersionUID進(jìn)行比較,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化,否則就會(huì)出現(xiàn)序列化版本不一致的異常。

      當(dāng)實(shí)現(xiàn)java.io.Serializable接口的實(shí)體(類)沒(méi)有顯式地定義一個(gè)名為serialVersionUID,類型為long的變量時(shí),Java序列化機(jī)制會(huì)根據(jù)編譯的class自動(dòng)生成一個(gè)serialVersionUID作序列化版本比較用,這種情況下,只有同一次編譯生成的class才會(huì)生成相同的serialVersionUID 。

      如果我們不希望通過(guò)編譯來(lái)強(qiáng)制劃分軟件版本,即實(shí)現(xiàn)序列化接口的實(shí)體能夠兼容先前版本,未作更改的類,就需要顯式地定義一個(gè)名為serialVersionUID,類型為long的變量,不修改這個(gè)變量值的序列化實(shí)體都可以相互進(jìn)行串行化和反串行化。

      相同功能代碼不同序列化 ID 的類對(duì)比,代碼如下:

      public class SerialVersionIDA implements Serializable {

      private static final long serialVersionUID=1L;

      private String name;

      public String getName() {

      return name;

      }

      public void setName(String name) {

      this.name = name;

      }

      public SerialVersionIDA() {

      }

      public SerialVersionIDA(String name) {

      this.name = name;

      }

      }

      public class SerialVersionIDA implements Serializable {

      private static final long serialVersionUID=2L;

      private String name;

      public String getName() {

      return name;

      }

      public void setName(String name) {

      this.name = name;

      }

      public SerialVersionIDA() {

      }

      public SerialVersionIDA(String name) {

      this.name = name;

      }

      }

      使用serialVersionUID為1L的類進(jìn)行序列化,而使用serialVersionUID為2L的類進(jìn)行反序列化,會(huì)提示異常,異常內(nèi)容為:

      java.io.InvalidClassException: com.sschen.Serializable.SerialVersionIDA; local class incompatible: stream classdesc serialVersionUID = 2, local class serialVersionUID = 1

      at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)

      at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)

      at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)

      at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)

      at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)

      at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)

      at com.sschen.Serializable.SerialVersionTest.main(SerialVersionTest.java:30)

      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

      at java.lang.reflect.Method.invoke(Method.java:498)

      at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

      序列化 ID 在 Eclipse 下提供了兩種生成策略,一個(gè)是固定的 1L,一個(gè)是隨機(jī)生成一個(gè)不重復(fù)的 long 類型數(shù)據(jù)(實(shí)際上是使用 JDK 工具生成),在這里有一個(gè)建議,如果沒(méi)有特殊需求,就是用默認(rèn)的 1L 就可以,這樣可以確保代碼一致時(shí)反序列化成功。那么隨機(jī)生成的序列化 ID 有什么作用呢,有些時(shí)候,通過(guò)改變序列化 ID 可以用來(lái)限制某些用戶的使用。

      特性使用案例

      讀者應(yīng)該聽過(guò) Fa?ade 模式,它是為應(yīng)用程序提供統(tǒng)一的訪問(wèn)接口,案例程序中的 Client 客戶端使用了該模式,案例程序結(jié)構(gòu)圖下圖所示。

      public class SerialStaticTest implements Serializable {

      private static final long serialVersionUID = 1L;

      public static int staticVar = 5;

      public static void main(String[] args) {

      try {

      File file = new File("/Users/sschen/Documents/student.txt");

      try {

      file.createNewFile();

      }

      catch(IOException e) {

      e.printStackTrace();

      }

      //初始時(shí)staticVar為5

      ObjectOutputStream out = new ObjectOutputStream(

      new FileOutputStream(file));

      out.writeObject(new SerialStaticTest());

      out.close();

      //序列化后修改為10

      SerialStaticTest.staticVar = 10;

      ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));

      SerialStaticTest t = (SerialStaticTest) oin.readObject();

      oin.close();

      //再讀取,通過(guò)t.staticVar打印新的值

      System.out.println(t.staticVar);

      } catch (FileNotFoundException e) {

      e.printStackTrace();

      } catch (IOException e) {

      e.printStackTrace();

      } catch (ClassNotFoundException e) {

      e.printStackTrace();

      }

      }

      }

      上面代碼中的 main 方法,將對(duì)象序列化保存到文件后,修改靜態(tài)變量的數(shù)值,再將序列化對(duì)象讀取出來(lái),然后通過(guò)讀取出來(lái)的對(duì)象獲得靜態(tài)變量的數(shù)值并打印出來(lái)。依照代碼,這個(gè) System.out.println(t.staticVar) 語(yǔ)句輸出的是 10 還是 5 呢?

      最后的輸出是 10,對(duì)于無(wú)法理解的讀者認(rèn)為,打印的 staticVar 是從讀取的對(duì)象里獲得的,應(yīng)該是保存時(shí)的狀態(tài)才對(duì)。之所以打印 10 的原因在于序列化時(shí),并不保存靜態(tài)變量,這其實(shí)比較容易理解,序列化保存的是對(duì)象的狀態(tài),靜態(tài)變量屬于類的狀態(tài),因此 序列化并不保存靜態(tài)變量。

      情境:一個(gè)子類實(shí)現(xiàn)了 Serializable 接口,它的父類都沒(méi)有實(shí)現(xiàn) Serializable 接口,序列化該子類對(duì)象,然后反序列化后輸出父類定義的某變量的數(shù)值,該變量數(shù)值與序列化時(shí)的數(shù)值不同。

      解決:要想將父類對(duì)象也序列化,就需要讓父類也實(shí)現(xiàn)Serializable 接口。如果父類不實(shí)現(xiàn)的話的,就 需要有默認(rèn)的無(wú)參的構(gòu)造函數(shù)。在父類沒(méi)有實(shí)現(xiàn) Serializable 接口時(shí),虛擬機(jī)是不會(huì)序列化父對(duì)象的,而一個(gè) Java 對(duì)象的構(gòu)造必須先有父對(duì)象,才有子對(duì)象,反序列化也不例外。所以反序列化時(shí),為了構(gòu)造父對(duì)象,只能調(diào)用父類的無(wú)參構(gòu)造函數(shù)作為默認(rèn)的父對(duì)象。因此當(dāng)我們?nèi)「笇?duì)象的變量值時(shí),它的值是調(diào)用父類無(wú)參構(gòu)造函數(shù)后的值。如果你考慮到這種序列化的情況,在父類無(wú)參構(gòu)造函數(shù)中對(duì)變量進(jìn)行初始化,否則的話,父類變量值都是默認(rèn)聲明的值,如 int 型的默認(rèn)是 0,string 型的默認(rèn)是 null。

      Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設(shè)為初始值,如 int 型的是 0,對(duì)象型的是 null。

      特性使用案例

      我們熟悉使用 Transient 關(guān)鍵字可以使得字段不被序列化,那么還有別的方法嗎?根據(jù)父類對(duì)象序列化的規(guī)則,我們可以將不需要被序列化的字段抽取出來(lái)放到父類中,子類實(shí)現(xiàn) Serializable 接口,父類不實(shí)現(xiàn),根據(jù)父類序列化規(guī)則,父類的字段數(shù)據(jù)將不被序列化,形成類圖如圖 2 所示。

      情境:服務(wù)器端給客戶端發(fā)送序列化對(duì)象數(shù)據(jù),對(duì)象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對(duì)該密碼字段在序列化時(shí),進(jìn)行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進(jìn)行反序列化時(shí),才可以對(duì)密碼進(jìn)行讀取,這樣可以一定程度保證序列化對(duì)象的數(shù)據(jù)安全。

      解決:在序列化過(guò)程中,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject 和 readObject 方法,進(jìn)行用戶自定義的序列化和反序列化,如果沒(méi)有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過(guò)程,比如可以在序列化的過(guò)程中動(dòng)態(tài)改變序列化的數(shù)值。基于這個(gè)原理,可以在實(shí)際應(yīng)用中得到使用,用于敏感字段的加密工作,下面的代碼展示了這個(gè)過(guò)程。

      public class SerialPwdTest implements Serializable {

      private static final long serialVersionUID = 1L;

      private String password = "pass";

      public String getPassword() {

      return password;

      }

      public void setPassword(String password) {

      this.password = password;

      }

      private void writeObject(ObjectOutputStream out) {

      try {

      ObjectOutputStream.PutField putFields = out.putFields();

      System.out.println("原密碼:" + password);

      password = "encryption";//模擬加密

      putFields.put("password", password);

      System.out.println("加密后的密碼" + password);

      out.writeFields();

      } catch (IOException e) {

      e.printStackTrace();

      }

      }

      private void readObject(ObjectInputStream in) {

      try {

      ObjectInputStream.GetField readFields = in.readFields();

      Object object = readFields.get("password", "");

      System.out.println("要解密的字符串:" + object.toString());

      password = "pass";//模擬解密,需要獲得本地的密鑰

      } catch (IOException e) {

      e.printStackTrace();

      } catch (ClassNotFoundException e) {

      e.printStackTrace();

      }

      }

      public static void main(String[] args) {

      File file = new File("/Users/sschen/Documents/student.txt");

      try {

      file.createNewFile();

      }

      catch(IOException e) {

      e.printStackTrace();

      }

      try {

      ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));

      out.writeObject(new SerialPwdTest());

      out.close();

      ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));

      SerialPwdTest t = (SerialPwdTest) oin.readObject();

      System.out.println("解密后的字符串:" + t.getPassword());

      oin.close();

      } catch (FileNotFoundException e) {

      e.printStackTrace();

      } catch (IOException e) {

      e.printStackTrace();

      } catch (ClassNotFoundException e) {

      e.printStackTrace();

      }

      }

      }

      SerialPwdTest的 writeObject 方法中,對(duì)密碼進(jìn)行了加密,在加密后進(jìn)行序列化保存到文件中,在 readObject 中則在讀取到密碼后,對(duì) password 進(jìn)行解密,只有擁有密鑰的客戶端,才可以正確的解析出密碼,確保了數(shù)據(jù)的安全。上面代碼的執(zhí)行結(jié)果為:

      原密碼:pass

      加密后的密碼encryption

      要解密的字符串:encryption

      解密后的字符串:pass

      特性使用案例

      RMI 技術(shù)是完全基于 Java 序列化技術(shù)的,服務(wù)器端接口調(diào)用所需要的參數(shù)對(duì)象來(lái)至于客戶端,它們通過(guò)網(wǎng)絡(luò)相互傳輸。這就涉及 RMI 的安全傳輸?shù)膯?wèn)題。一些敏感的字段,如用戶名密碼(用戶登錄時(shí)需要對(duì)密碼進(jìn)行傳輸),我們希望對(duì)其進(jìn)行加密,這時(shí),就可以采用本節(jié)介紹的方法在客戶端對(duì)密碼進(jìn)行加密,服務(wù)器端進(jìn)行解密,確保數(shù)據(jù)傳輸?shù)陌踩浴?/p>

      情境:?jiǎn)栴}代碼如清單 4 所示。

      清單 4. 存儲(chǔ)規(guī)則問(wèn)題代碼

      public class SerialSaveTest implements Serializable {

      public static void main(String[] args) {

      File file = new File("/Users/sschen/Documents/student.txt");

      try {

      file.createNewFile();

      }

      catch(IOException e) {

      e.printStackTrace();

      }

      try {

      ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));

      SerialSaveTest test = new SerialSaveTest();

      //試圖將對(duì)象兩次寫入文件

      out.writeObject(test);

      out.flush();

      System.out.println(file.length());

      out.writeObject(test);

      out.close();

      System.out.println(file.length());

      ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));

      //從文件依次讀出兩個(gè)文件

      SerialSaveTest t1 = (SerialSaveTest) oin.readObject();

      SerialSaveTest t2 = (SerialSaveTest) oin.readObject();

      oin.close();

      //判斷兩個(gè)引用是否指向同一個(gè)對(duì)象

      System.out.println(t1 == t2);

      } catch (IOException e) {

      e.printStackTrace();

      } catch (ClassNotFoundException e) {

      e.printStackTrace();

      }

      }

      }

      清單4中對(duì)同一對(duì)象兩次寫入文件,打印出寫入一次對(duì)象后的存儲(chǔ)大小和寫入兩次后的存儲(chǔ)大小,然后從文件中反序列化出兩個(gè)對(duì)象,比較這兩個(gè)對(duì)象是否為同一對(duì)象。一般的思維是,兩次寫入對(duì)象,文件大小會(huì)變?yōu)閮杀兜拇笮。葱蛄谢瘯r(shí),由于從文件讀取,生成了兩個(gè)對(duì)象,判斷相等時(shí)應(yīng)該是輸入 false 才對(duì),但是最后結(jié)果輸出如下:

      59

      64

      true

      我們看到,第二次寫入對(duì)象時(shí)文件只增加了 5 字節(jié),并且兩個(gè)對(duì)象是相等的,這是為什么呢?

      解答:Java 序列化機(jī)制為了節(jié)省磁盤空間,具有特定的存儲(chǔ)規(guī)則,當(dāng)寫入文件的為同一對(duì)象時(shí),并不會(huì)再將對(duì)象的內(nèi)容進(jìn)行存儲(chǔ),而只是再次存儲(chǔ)一份引用,上面增加的 5 字節(jié)的存儲(chǔ)空間就是新增引用和一些控制信息的空間。反序列化時(shí),恢復(fù)引用關(guān)系,使得清單 3 中的 t1 和 t2 指向唯一的對(duì)象,二者相等,輸出 true。該存儲(chǔ)規(guī)則極大的節(jié)省了存儲(chǔ)空間。

      特性案例分析

      查看清單 5 的代碼。

      清單 5. 案例代碼

      public class SerialSaveTest implements Serializable {

      private int id;

      public int getId() {

      return id;

      }

      public void setId(int id) {

      this.id = id;

      }

      public static void main(String[] args) {

      File file = new File("/Users/sschen/Documents/student.txt");

      try {

      file.createNewFile();

      }

      catch(IOException e) {

      e.printStackTrace();

      }

      try {

      ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));

      SerialSaveTest test = new SerialSaveTest();

      test.setId(1);

      //試圖將對(duì)象兩次寫入文件

      out.writeObject(test);

      out.flush();

      System.out.println(file.length());

      test.setId(5);

      out.writeObject(test);

      out.close();

      System.out.println(file.length());

      ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));

      //從文件依次讀出兩個(gè)文件

      SerialSaveTest t1 = (SerialSaveTest) oin.readObject();

      SerialSaveTest t2 = (SerialSaveTest) oin.readObject();

      oin.close();

      //判斷兩個(gè)引用是否指向同一個(gè)對(duì)象

      System.out.println(t1 == t2);

      System.out.println(t1.getId());

      System.out.println(t2.getId());

      } catch (IOException e) {

      e.printStackTrace();

      } catch (ClassNotFoundException e) {

      e.printStackTrace();

      }

      }

      }

      清單 4 的目的是希望將 test 對(duì)象兩次保存到/Users/sschen/Documents/student.txt文件中,寫入一次以后修改對(duì)象屬性值再次保存第二次,然后從/Users/sschen/Documents/student.txt中再依次讀出兩個(gè)對(duì)象,輸出這兩個(gè)對(duì)象的 i 屬性值。案例代碼的目的原本是希望一次性傳輸對(duì)象修改前后的狀態(tài)。

      結(jié)果兩個(gè)輸出的都是 1, 原因就是第一次寫入對(duì)象以后,第二次再試圖寫的時(shí)候,虛擬機(jī)根據(jù)引用關(guān)系知道已經(jīng)有一個(gè)相同對(duì)象已經(jīng)寫入文件,因此只保存第二次寫的引用,所以讀取時(shí),都是第一次保存的對(duì)象。讀者在使用一個(gè)文件多次 writeObject 需要特別注意這個(gè)問(wèn)題。

      Java 網(wǎng)絡(luò)

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:css簡(jiǎn)介
      下一篇:只讀模式怎么關(guān)閉(取消只讀模式)
      相關(guān)文章
      亚洲精品天堂在线观看| 亚洲人成电影福利在线播放| 久久久亚洲欧洲日产国码是AV| 亚洲一级黄色视频| 国产亚洲精品第一综合| 一本色道久久综合亚洲精品蜜桃冫 | 亚洲中文久久精品无码1| 亚洲成人免费在线| 久久夜色精品国产亚洲AV动态图 | 国产亚洲高清在线精品不卡| 亚洲av无码无线在线观看| 亚洲精品国产精品| 日本亚洲中午字幕乱码| 亚洲精品乱码久久久久蜜桃| 亚洲国产精品免费观看| 亚洲国产欧美一区二区三区| 亚洲国产成人久久精品软件 | 亚洲av中文无码乱人伦在线咪咕| 亚洲精品制服丝袜四区| 国产亚洲精品精华液| 亚洲AV无码欧洲AV无码网站| 亚洲av无码乱码国产精品| 亚洲91av视频| 亚洲国产精品成人精品软件| 亚洲三级在线播放| 亚洲日韩国产二区无码| 午夜亚洲国产理论片二级港台二级| 亚洲AV无码一区二区三区电影| 青青青国产色视频在线观看国产亚洲欧洲国产综合| 亚洲AV日韩综合一区| 亚洲精品无码你懂的网站| 日韩精品亚洲aⅴ在线影院| 国产精品亚洲а∨无码播放 | 亚洲成av人在线观看网站| 在线亚洲精品视频| 久久青青草原亚洲av无码| 亚洲国产精品成人久久| 亚洲精彩视频在线观看| 亚洲an日韩专区在线| 中文字幕亚洲综合小综合在线| 亚洲欧美日韩综合俺去了|