Java序列化介紹-創(chuàng)新互聯(lián)

引言

在持久化數(shù)據(jù)對(duì)象的時(shí)候我們很少使用Java序列化,而是使用數(shù)據(jù)庫等方式來實(shí)現(xiàn)。但是在我看來,Java 序列化是一個(gè)很重要的內(nèi)容,序列化不僅可以保存對(duì)象到磁盤進(jìn)行持久化,還可以通過網(wǎng)絡(luò)傳輸。在平時(shí)的面試當(dāng)中,序列化也是經(jīng)常被談及的一塊內(nèi)容。

創(chuàng)新互聯(lián)專注于網(wǎng)站建設(shè)|成都網(wǎng)站維護(hù)公司|優(yōu)化|托管以及網(wǎng)絡(luò)推廣,積累了大量的網(wǎng)站設(shè)計(jì)與制作經(jīng)驗(yàn),為許多企業(yè)提供了網(wǎng)站定制設(shè)計(jì)服務(wù),案例作品覆蓋食品包裝袋等行業(yè)。能根據(jù)企業(yè)所處的行業(yè)與銷售的產(chǎn)品,結(jié)合品牌形象的塑造,量身策劃品質(zhì)網(wǎng)站。

談到序列化時(shí),大家可能知道將類實(shí)現(xiàn)Serializable接口就可以達(dá)到序列化的目的,但當(dāng)看到關(guān)于序列化的面試題時(shí)我們卻常常一臉懵逼。

1)可序列化接口和可外部接口的區(qū)別是什么?

2)序列化時(shí),你希望某些成員不要序列化?該如何實(shí)現(xiàn)?

3)什么是 serialVersionUID ?如果不定義serialVersionUID,會(huì)發(fā)生什么?

是不是突然發(fā)現(xiàn)我們對(duì)這些問題其實(shí)都還存在很多疑惑?本文將總結(jié)一些Java序列化的常見問題,并且通過demo來進(jìn)行測試和解答。

問題一:什么是 Java 序列化?

序列化是把對(duì)象改成可以存到磁盤或通過網(wǎng)絡(luò)發(fā)送到其它運(yùn)行中的 Java 虛擬機(jī)的二進(jìn)制格式的過程,并可以通過反序列化恢復(fù)對(duì)象狀態(tài)。Java 序列化API給開發(fā)人員提供了一個(gè)標(biāo)準(zhǔn)機(jī)制:通過實(shí)現(xiàn) java.io.Serializable 或者 java.io.Externalizable 接口,ObjectInputStream 及ObjectOutputStream 處理對(duì)象序列化。實(shí)現(xiàn)java.io.Externalizable 接口的話,Java 程序員可自由選擇基于類結(jié)構(gòu)的標(biāo)準(zhǔn)序列化或是它們自定義的二進(jìn)制格式,通常認(rèn)為后者才是最佳實(shí)踐,因?yàn)樾蛄谢亩M(jìn)制文件格式成為類輸出 API的一部分,可能破壞 Java 中私有和包可見的屬性的封裝。

序列化到底有什么用?

實(shí)現(xiàn) java.io.Serializable。

定義用戶類:

class User implements Serializable {
   private String username;
   private String passwd;

   public String getUsername() {
     return username;
   }

   public void setUsername(String username) {
     this.username = username;
   }

   public String getPasswd() {
     return passwd;
   }

   public void setPasswd(String passwd) {
     this.passwd = passwd;
   }
}

我們把對(duì)象序列化,通過ObjectOutputStream存儲(chǔ)到txt文件中,再通過ObjectInputStream讀取txt文件,反序列化成User對(duì)象。

public class TestSerialize {

   public static void main(String[] args) {

     User user = new User();
     user.setUsername("hengheng");
     user.setPasswd("123456");

     System.out.println("read before Serializable: ");
     System.out.println("username: " + user.getUsername());
     System.err.println("password: " + user.getPasswd());

     try {
       ObjectOutputStream os = new ObjectOutputStream(
           new FileOutputStream("/Users/admin/Desktop/test/user.txt"));
       os.writeObject(user); // 將User對(duì)象寫進(jìn)文件
       os.flush();
       os.close();
     } catch (FileNotFoundException e) {
       e.printStackTrace();
     } catch (IOException e) {
       e.printStackTrace();
     }
     try {
       ObjectInputStream is = new ObjectInputStream(new FileInputStream(
           "/Users/admin/Desktop/test/user.txt"));
       user = (User) is.readObject(); // 從流中讀取User的數(shù)據(jù)
       is.close();

       System.out.println("\nread after Serializable: ");
       System.out.println("username: " + user.getUsername());
       System.err.println("password: " + user.getPasswd());

     } catch (FileNotFoundException e) {
       e.printStackTrace();
     } catch (IOException e) {
       e.printStackTrace();
     } catch (ClassNotFoundException e) {
       e.printStackTrace();
     }
   }
}

運(yùn)行結(jié)果如下:

序列化前數(shù)據(jù): 
username: hengheng
password: 123456

序列化后數(shù)據(jù): 
username: hengheng
password: 123456

到這里,我們大概知道了什么是序列化。

問題二:序列化時(shí),你希望某些成員不要序列化,該如何實(shí)現(xiàn)?

答案:聲明該成員為靜態(tài)或瞬態(tài),在 Java 序列化過程中則不會(huì)被序列化。

  • 靜態(tài)變量:加static關(guān)鍵字。
  • 瞬態(tài)變量: 加transient關(guān)鍵字。

我們先嘗試把變量聲明為瞬態(tài)。

class User implements Serializable {
   private String username;
   private transient String passwd;

   public String getUsername() {
     return username;
   }

   public void setUsername(String username) {
     this.username = username;
   }

   public String getPasswd() {
     return passwd;
   }

   public void setPasswd(String passwd) {
     this.passwd = passwd;
   }

在密碼字段前加上了transient關(guān)鍵字再運(yùn)行。運(yùn)行結(jié)果:

序列化前數(shù)據(jù): 
username: hengheng
password: 123456

序列化后數(shù)據(jù): 
username: hengheng
password: null

通過運(yùn)行結(jié)果發(fā)現(xiàn)密碼沒有被序列化,達(dá)到了我們的目的。

再嘗試在用戶名前加static關(guān)鍵字。

class User implements Serializable {
   private static String username;
   private transient String passwd;

   public String getUsername() {
     return username;
   }

   public void setUsername(String username) {
     this.username = username;
   }

   public String getPasswd() {
     return passwd;
   }

   public void setPasswd(String passwd) {
     this.passwd = passwd;
   }

運(yùn)行結(jié)果:

序列化前數(shù)據(jù): 
username: hengheng
password: 123456

序列化后數(shù)據(jù): 
username: hengheng
password: null

我們發(fā)現(xiàn)運(yùn)行后的結(jié)果和預(yù)期的不一樣,按理說username也應(yīng)該變?yōu)閚ull才對(duì)。是什么原因呢?

原因是:反序列化后類中static型變量username的值為當(dāng)前JVM中對(duì)應(yīng)的靜態(tài)變量的值,而不是反序列化得出的。

我們來證明一下:

public class TestSerialize {

   public static void main(String[] args) {

     User user = new User();
     user.setUsername("hengheng");
     user.setPasswd("123456");

     System.out.println("序列化前數(shù)據(jù): ");
     System.out.println("username: " + user.getUsername());
     System.err.println("password: " + user.getPasswd());

     try {
       ObjectOutputStream os = new ObjectOutputStream(
           new FileOutputStream("/Users/admin/Desktop/test/user.txt"));
       os.writeObject(user); // 將User對(duì)象寫進(jìn)文件
       os.flush();
       os.close();
     } catch (FileNotFoundException e) {
       e.printStackTrace();
     } catch (IOException e) {
       e.printStackTrace();
     }
     User.username = "小明";
     try {
       ObjectInputStream is = new ObjectInputStream(new FileInputStream(
           "/Users/admin/Desktop/test/user.txt"));
       user = (User) is.readObject(); // 從流中讀取User的數(shù)據(jù)
       is.close();

       System.out.println("\n序列化后數(shù)據(jù): ");
       System.out.println("username: " + user.getUsername());
       System.err.println("password: " + user.getPasswd());

     } catch (FileNotFoundException e) {
       e.printStackTrace();
     } catch (IOException e) {
       e.printStackTrace();
     } catch (ClassNotFoundException e) {
       e.printStackTrace();
     }
   }
}

class User implements Serializable {
   public static String username;
   private transient String passwd;

   public String getUsername() {
     return username;
   }

   public void setUsername(String username) {
     this.username = username;
   }

   public String getPasswd() {
     return passwd;
   }

   public void setPasswd(String passwd) {
     this.passwd = passwd;
   }
}

在反序列化前把靜態(tài)變量username的值改為『小明』。

User.username = "小明";

再運(yùn)行一次:

序列化前數(shù)據(jù): 
username: hengheng
password: 123456

序列化后數(shù)據(jù): 
username: 小明
password: null

果然,這里的username是JVM中靜態(tài)變量的值,并不是反序列化得到的值。

問題三:serialVersionUID有什么用?

我們經(jīng)常會(huì)在類中自定義一個(gè)serialVersionUID:

private static final long serialVersionUID = 8294180014912103005L

這個(gè)serialVersionUID有什么用呢?如果不設(shè)置的話會(huì)有什么后果?

serialVersionUID 是一個(gè) private static final long 型 ID,當(dāng)它被印在對(duì)象上時(shí),它通常是對(duì)象的哈希碼。serialVersionUID可以自己定義,也可以自己去生成。

不指定 serialVersionUID的后果是:當(dāng)你添加或修改類中的任何字段時(shí),已序列化類將無法恢復(fù),因?yàn)樾骂惡团f序列化對(duì)象生成的 serialVersionUID 將有所不同。Java 序列化的過程是依賴于正確的序列化對(duì)象恢復(fù)狀態(tài)的,并在序列化對(duì)象序列版本不匹配的情況下引發(fā) java.io.InvalidClassException 無效類異常。

舉個(gè)例子大家就明白了:

我們保持之前保存的序列化文件不變,然后修改User類。

class User implements Serializable {
   public static String username;
   private transient String passwd;
   private String age;

   public String getUsername() {
     return username;
   }

   public void setUsername(String username) {
     this.username = username;
   }

   public String getPasswd() {
     return passwd;
   }

   public void setPasswd(String passwd) {
     this.passwd = passwd;
   }

   public String getAge() {
     return age;
   }

   public void setAge(String age) {
     this.age = age;
   }
}

加了一個(gè)屬性age,然后單另寫一個(gè)反序列化的方法:

public static void main(String[] args) {
     try {
       ObjectInputStream is = new ObjectInputStream(new FileInputStream(
           "/Users/admin/Desktop/test/user.txt"));
       User user = (User) is.readObject(); // 從流中讀取User的數(shù)據(jù)
       is.close();

       System.out.println("\n修改User類之后的數(shù)據(jù): ");
       System.out.println("username: " + user.getUsername());
       System.err.println("password: " + user.getPasswd());

     } catch (FileNotFoundException e) {
       e.printStackTrace();
     } catch (IOException e) {
       e.printStackTrace();
     } catch (ClassNotFoundException e) {
       e.printStackTrace();
     }
   }

Java序列化介紹

報(bào)錯(cuò)了,我們發(fā)現(xiàn)之前的User類生成的serialVersionUID和修改后的serialVersionUID不一樣(因?yàn)槭峭ㄟ^對(duì)象的哈希碼生成的),導(dǎo)致了InvalidClassException異常。

自定義serialVersionUID:

class User implements Serializable {
   private static final long serialVersionUID = 4348344328769804325L;

   public static String username;
   private transient String passwd;
   private String age;

   public String getUsername() {
     return username;
   }

   public void setUsername(String username) {
     this.username = username;
   }

   public String getPasswd() {
     return passwd;
   }

   public void setPasswd(String passwd) {
     this.passwd = passwd;
   }

   public String getAge() {
     return age;
   }

   public void setAge(String age) {
     this.age = age;
   }
}

再試一下:

序列化前數(shù)據(jù): 
username: hengheng
password: 123456

序列化后數(shù)據(jù): 
username: 小明
password: null

運(yùn)行結(jié)果無報(bào)錯(cuò),所以一般都要自定義serialVersionUID。

問題四:是否可以自定義序列化過程?

答案當(dāng)然是可以的。

之前我們介紹了序列化的第二種方式:

實(shí)現(xiàn)Externalizable接口,然后重寫writeExternal() 和readExternal()方法,這樣就可以自定義序列化。

比如我們嘗試把變量設(shè)為瞬態(tài)。

public class ExternalizableTest implements Externalizable {

   private transient String content = "我是被transient修飾的變量哦";

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {
     out.writeObject(content);
   }

   @Override
   public void readExternal(ObjectInput in) throws IOException,
       ClassNotFoundException {
     content = (String) in.readObject();
   }

   public static void main(String[] args) throws Exception {

     ExternalizableTest et = new ExternalizableTest();
     ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
         new File("test")));
     out.writeObject(et);

     ObjectInput in = new ObjectInputStream(new FileInputStream(new File(
         "test")));
     et = (ExternalizableTest) in.readObject();
     System.out.println(et.content);

     out.close();
     in.close();
   }
}

運(yùn)行結(jié)果:

我是被transient修飾的變量哦

這里實(shí)現(xiàn)的是Externalizable接口,則沒有任何東西可以自動(dòng)序列化,需要在writeExternal方法中進(jìn)行手工指定所要序列化的變量,這與是否被transient修飾無關(guān)。

通過上述介紹,是不是對(duì)Java序列化有了更多的了解?

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。

文章名稱:Java序列化介紹-創(chuàng)新互聯(lián)
網(wǎng)頁地址:http://muchs.cn/article18/ejodp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、ChatGPT面包屑導(dǎo)航、電子商務(wù)營銷型網(wǎng)站建設(shè)、品牌網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名