国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Java Serializable系列化與反系列化

2019-11-06 06:18:36
字體:
供稿:網(wǎng)友

學(xué)習(xí)java的同學(xué)注意了!!! 學(xué)習(xí)過程中遇到什么問題或者想獲取學(xué)習(xí)資源的話,歡迎加入Java學(xué)習(xí)交流群,群號(hào)碼:523047986  我們一起學(xué)Java!

【引言】

    將 Java 對(duì)象序列化為二進(jìn)制文件的 Java 序列化技術(shù)是 Java 系列技術(shù)中一個(gè)較為重要的技術(shù)點(diǎn),在大部分情況下,開發(fā)人員只需要了解被序列化的類需要實(shí)現(xiàn) Serializable 接口,使用 ObjectInputStream 和 ObjectOutputStream 進(jìn)行對(duì)象的讀寫。然而在有些情況下,光知道這些還遠(yuǎn)遠(yuǎn)不夠,文章列舉了筆者遇到的一些真實(shí)情境,它們與 Java 序列化相關(guān),通過分析情境出現(xiàn)的原因,使讀者輕松牢記 Java 序列化中的一些高級(jí)認(rèn)識(shí)。

【系列化serialVersionUID問題】

    在Java系列化與反系列化中,虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 ID 是否一致(就是 PRivate static final long serialVersionUID = 1L),如果serialVersionUID不同,你將得到一個(gè)java.io.InvalidClassException,看如下代碼:[java] view plain copypackage wen.hui.test.serializable;    import java.io.Serializable;    /**  * serializable測(cè)試  *  * @author whwang  * 2011-12-1 下午09:50:07  */  public class A implements Serializable {        private static final long serialVersionUID = 2L;        public A() {        }        public void print() {          System.err.println("test serializable");      }        public static void main(String[] args) throws Exception {        }  }  [java] view plain copypackage wen.hui.test.serializable;    import java.io.FileInputStream;  import java.io.FileOutputStream;  import java.io.ObjectInputStream;  import java.io.ObjectOutputStream;    /**  *  * @author whwang  * 2011-12-1 下午09:54:36  */  public class Test1 {        public static void main(String[] args) throws Exception {          // write object          String fileName = "obj";          toWrite(fileName);            // read object          toRead(fileName);      }        public static void toWrite(String fileName) throws Exception {          ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(                  fileName));          oos.writeObject(new A());          oos.close();      }        public static void toRead(String fileName) throws Exception {          ObjectInputStream ois = new ObjectInputStream(                  new FileInputStream("obj"));          A t = (A) ois.readObject();          t.print();          ois.close();      }    }  1、直接運(yùn)行Test1的main方法,運(yùn)行正確;2、先將Test1的main方法中的toRead(fileName)注釋,把類A中的serialVersionUID 值改為1,運(yùn)行Test1;然后在代開toRead(fileName),將toWrite(fileName)注釋,同時(shí)將類A中的serialVersionUID 值改為2;運(yùn)行Test1,發(fā)現(xiàn)拋出異常,表明如果serialVersionUID不同,即使兩個(gè)“完全”相同的類也無法反序列化。[java] view plain copyException in thread "main" java.io.InvalidClassException: wen.hui.test.serializable.A; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2  序列化 ID 在 Eclipse 下提供了兩種生成策略,一個(gè)是固定的 1L,一個(gè)是隨機(jī)生成一個(gè)不重復(fù)的 long 類型數(shù)據(jù)(實(shí)際上是使用 JDK 工具生成),在這里有一個(gè)建議,如果沒有特殊需求,就是用默認(rèn)的 1L 就可以,這樣可以確保代碼一致時(shí)反序列化成功。那么隨機(jī)生成的序列化 ID 有什么作用呢,有些時(shí)候,通過改變序列化 ID 可以用來限制某些用戶的使用。如Facade模式中,Client 端通過 Fa?ade Object 才可以與業(yè)務(wù)邏輯對(duì)象進(jìn)行交互。而客戶端的 Fa?ade Object 不能直接由 Client 生成,而是需要 Server 端生成,然后序列化后通過網(wǎng)絡(luò)將二進(jìn)制對(duì)象數(shù)據(jù)傳給 Client,Client 負(fù)責(zé)反序列化得到 Fa?ade 對(duì)象。該模式可以使得 Client 端程序的使用需要服務(wù)器端的許可,同時(shí) Client 端和服務(wù)器端的 Fa?ade Object 類需要保持一致。當(dāng)服務(wù)器端想要進(jìn)行版本更新時(shí),只要將服務(wù)器端的 Fa?ade Object 類的序列化 ID 再次生成,當(dāng) Client 端反序列化 Fa?ade Object 就會(huì)失敗,也就是強(qiáng)制 Client 端從服務(wù)器端獲取最新程序。

【靜態(tài)變量序列】

直接看代碼:[java] view plain copypackage wen.hui.test.serializable;    import java.io.Serializable;    /**  * serializable測(cè)試  *  * @author whwang  * 2011-12-1 下午09:50:07  */  public class A implements Serializable {        private static final long serialVersionUID = 1L;        public static int staticVar = 10;        public A() {        }        public void print() {          System.err.println("test serializable");      }        public static void main(String[] args) throws Exception {        }  }  [java] view plain copypackage wen.hui.test.serializable;    import java.io.FileInputStream;  import java.io.FileNotFoundException;  import java.io.FileOutputStream;  import java.io.IOException;  import java.io.ObjectInputStream;  import java.io.ObjectOutputStream;    /**  * 序列化保存的是對(duì)象的狀態(tài),靜態(tài)變量屬于類的狀態(tài),不會(huì)被序列化  * @author whwang  * 2011-12-1 下午10:12:06  */  public class Test2 {        public static void main(String[] args) {          try {              // 初始時(shí)staticVar為10              ObjectOutputStream out = new ObjectOutputStream(                      new FileOutputStream("obj"));              out.writeObject(new A());              out.close();                // 序列化后修改為100              A.staticVar = 100;                ObjectInputStream oin = new ObjectInputStream(new FileInputStream(                      "obj"));              A t = (A) oin.readObject();              oin.close();                // 再讀取,通過t.staticVar打印新的值              System.err.println(t.staticVar);            } catch (FileNotFoundException e) {              e.printStackTrace();          } catch (IOException e) {              e.printStackTrace();          } catch (ClassNotFoundException e) {              e.printStackTrace();          }      }    }  A類的靜態(tài)字段staticVar初始化值為10,在Teste2的main方法中,將A類的一個(gè)實(shí)例系列化到硬盤,然后修改靜態(tài)字段staticVar = 100,接著反系列化剛系列化的對(duì)象,輸出”該對(duì)象的“staticVar的值。輸出的是 100 還是 10 呢?結(jié)果輸出是100,之所以打印 100 的原因在于序列化時(shí),并不保存靜態(tài)變量,這其實(shí)比較容易理解,序列化保存的是對(duì)象的狀態(tài),靜態(tài)變量屬于類的狀態(tài),因此 序列化并不保存靜態(tài)變量。

【父類的序列化與 Transient 關(guān)鍵字】

情境:一個(gè)子類實(shí)現(xiàn)了 Serializable 接口,它的父類都沒有實(shí)現(xiàn) Serializable 接口,序列化該子類對(duì)象,然后反序列化后輸出父類定義的某變量的數(shù)值,該變量數(shù)值與序列化時(shí)的數(shù)值不同。解決:要想將父類對(duì)象也序列化,就需要讓父類也實(shí)現(xiàn)Serializable 接口。如果父類不實(shí)現(xiàn)的話的,就 需要有默認(rèn)的無參的構(gòu)造函數(shù)。在父類沒有實(shí)現(xiàn) Serializable 接口時(shí),虛擬機(jī)是不會(huì)序列化父對(duì)象的,而一個(gè) Java 對(duì)象的構(gòu)造必須先有父對(duì)象,才有子對(duì)象,反序列化也不例外。所以反序列化時(shí),為了構(gòu)造父對(duì)象,只能調(diào)用父類的無參構(gòu)造函數(shù)作為默認(rèn)的父對(duì)象。因此當(dāng)我們?nèi)「笇?duì)象的變量值時(shí),它的值是調(diào)用父類無參構(gòu)造函數(shù)后的值。如果你考慮到這種序列化的情況,在父類無參構(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。[java] view plain copypackage wen.hui.test.serializable;    /**  *  * @author whwang  * 2011-12-1 下午10:23:10  */  public class B {      public int b1;        public int b2;        public B() {          this.b2 = 100;      }  }  [java] view plain copypackage wen.hui.test.serializable;    import java.io.Serializable;    /**  *  * @author whwang  * 2011-12-1 下午09:49:51  */  public class C extends B implements Serializable {        private static final long serialVersionUID = 1L;        public int c1;        public int c2;        public C() {          // 給b1,b2賦值          this.b1 = 1;          this.b2 = 2;          this.c2 = -100;      }    }  [java] view plain copypackage wen.hui.test.serializable;    import java.io.FileInputStream;  import java.io.FileNotFoundException;  import java.io.FileOutputStream;  import java.io.IOException;  import java.io.ObjectInputStream;  import java.io.ObjectOutputStream;    /**  * 如果父類沒有實(shí)現(xiàn)Serializable,那么父類不會(huì)被系列化,當(dāng)反系列化子類時(shí)  * 會(huì)調(diào)用父類無參的構(gòu)造方法。  * @author whwang  * 2011-12-1 下午10:23:51  */  public class Test3 {        public static void main(String[] args) {          try {              ObjectOutputStream out = new ObjectOutputStream(                      new FileOutputStream("obj"));              out.writeObject(new C());              out.close();                ObjectInputStream oin = new ObjectInputStream(new FileInputStream(                      "obj"));              C t = (C) oin.readObject();              oin.close();                System.err.println(t.b1 + ", " + t.b2 + ", " + t.c1 + ", " + t.c2);            } catch (FileNotFoundException e) {              e.printStackTrace();          } catch (IOException e) {              e.printStackTrace();          } catch (ClassNotFoundException e) {              e.printStackTrace();          }      }    }  運(yùn)行Test3的main方法,結(jié)果輸出0, 100, 0, -100;即在子類的構(gòu)造方法中對(duì)父類的成員變量的初始化沒有被系列化;而反系列化時(shí),則是調(diào)用父類的無參構(gòu)造方法實(shí)例化父類。

【對(duì)敏感字段加密】

情境:服務(wù)器端給客戶端發(fā)送序列化對(duì)象數(shù)據(jù),對(duì)象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對(duì)該密碼字段在序列化時(shí),進(jìn)行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進(jìn)行反序列化時(shí),才可以對(duì)密碼進(jìn)行讀取,這樣可以一定程度保證序列化對(duì)象的數(shù)據(jù)安全。解決:在序列化過程中,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject(ObjectOutputStread out) 和 readObject(ObjectInputStread in) 方法(通過反射機(jī)制),進(jìn)行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動(dòng)態(tài)改變序列化的數(shù)值。基于這個(gè)原理,可以在實(shí)際應(yīng)用中得到使用,用于敏感字段的加密工作,如:[java] view plain copypackage wen.hui.test.serializable;    import java.io.IOException;  import java.io.ObjectInputStream;  import java.io.ObjectInputStream.GetField;  import java.io.Serializable;    /**  * @author whwang 2011-12-1 下午10:29:54  */  public class D implements Serializable {        private static final long serialVersionUID = 1L;            private String passWord;        public D() {        }        public String getPassword() {          return password;      }        public void setPassword(String password) {          this.password = password;      }    //    private void writeObject(ObjectOutputStream out) {  //          //    }        private void readObject(ObjectInputStream in) {          try {              GetField readFields = in.readFields();              Object object = readFields.get("password", "");              System.err.println("要解密的字符串:" + object.toString());              password = "password";// 模擬解密,需要獲得本地的密鑰          } catch (IOException e) {              e.printStackTrace();          } catch (ClassNotFoundException e) {              e.printStackTrace();          }        }    }  [java] view plain copypackage wen.hui.test.serializable;    import java.io.FileInputStream;  import java.io.FileNotFoundException;  import java.io.FileOutputStream;  import java.io.IOException;  import java.io.ObjectInputStream;  import java.io.ObjectOutputStream;    /**  * @ClassName: Test4  * @Description: 加密測(cè)試。  * @author whwang  * @date 2011-12-1 下午05:01:34  *  */  public class Test4 {        public static void main(String[] args) {          try {              ObjectOutputStream out = new ObjectOutputStream(                      new FileOutputStream("obj"));              D t1 = new D();              t1.setPassword("encryption");// 加密后的(模擬)              out.writeObject(t1);              out.close();                ObjectInputStream oin = new ObjectInputStream(new FileInputStream(                      "obj"));              D t = (D) oin.readObject();              oin.close();                System.err.println("解密后的字符串:" + t.getPassword());                        } catch (FileNotFoundException e) {              e.printStackTrace();          } catch (IOException e) {              e.printStackTrace();          } catch (ClassNotFoundException e) {              e.printStackTrace();          }      }        }  在系列化之前,將密碼字段加密,然后系列化到硬盤,在反系列化時(shí),通過類D中的readObject(ObjectInputStream in)做解密操作,確保了數(shù)據(jù)的安全。如:RMI 技術(shù)是完全基于 Java 序列化技術(shù)的,服務(wù)器端接口調(diào)用所需要的參數(shù)對(duì)象來至于客戶端,它們通過網(wǎng)絡(luò)相互傳輸。這就涉及 RMI 的安全傳輸?shù)膯栴}。一些敏感的字段,如用戶名密碼(用戶登錄時(shí)需要對(duì)密碼進(jìn)行傳輸),我們希望對(duì)其進(jìn)行加密,這時(shí),就可以采用本節(jié)介紹的方法在客戶端對(duì)密碼進(jìn)行加密,服務(wù)器端進(jìn)行解密,確保數(shù)據(jù)傳輸?shù)陌踩浴?h3 style="margin:0px; padding:0px; color:rgb(51,51,51); font-family:Arial; line-height:26px">【序列化存儲(chǔ)規(guī)則】請(qǐng)看如下代碼:[java] view plain copypackage wen.hui.test.serializable;    import java.io.File;  import java.io.FileInputStream;  import java.io.FileNotFoundException;  import java.io.FileOutputStream;  import java.io.IOException;  import java.io.ObjectInputStream;  import java.io.ObjectOutputStream;    /**  *  * @author whwang 2011-12-1 下午11:00:35  */  public class Test5 {        public static void main(String[] args) {          ObjectOutputStream out;          try {              out = new ObjectOutputStream(new FileOutputStream(                      "obj"));              A t = new A();              // 試圖將對(duì)象兩次寫入文件              out.writeObject(t);              out.flush();              System.err.println("加入第一個(gè)類:" + new File("obj").length());              //t.a = 10;              out.writeObject(t);              out.close();              System.err.println("加入第二個(gè)類:" + new File("obj").length());                ObjectInputStream oin = new ObjectInputStream(new FileInputStream(                      "obj"));              // 從文件依次讀出兩個(gè)文件              A t1 = (A) oin.readObject();              A t2 = (A) oin.readObject();              oin.close();                // 判斷兩個(gè)引用是否指向同一個(gè)對(duì)象              System.err.println(t1 == t2);                        } catch (FileNotFoundException e) {              e.printStackTrace();          } catch (IOException e) {              e.printStackTrace();          } catch (ClassNotFoundException e) {              e.printStackTrace();          }      }    }  對(duì)同一對(duì)象兩次寫入文件,打印出寫入一次對(duì)象后的存儲(chǔ)大小和寫入兩次后的存儲(chǔ)大小,然后從文件中反序列化出兩個(gè)對(duì)象,比較這兩個(gè)對(duì)象是否為同一對(duì)象。一般的思維是,兩次寫入對(duì)象,文件大小會(huì)變?yōu)閮杀兜拇笮。葱蛄谢瘯r(shí),由于從文件讀取,生成了兩個(gè)對(duì)象,判斷相等時(shí)應(yīng)該是輸入 false 才對(duì)。但實(shí)際結(jié)果是:第二次寫入對(duì)象時(shí)文件只增加了 5 字節(jié),并且兩個(gè)對(duì)象是相等的,這是為什么呢?解答:Java 序列化機(jī)制為了節(jié)省磁盤空間,具有特定的存儲(chǔ)規(guī)則,當(dāng)寫入文件的為同一對(duì)象時(shí)(根據(jù)包名+類名),并不會(huì)再將對(duì)象的內(nèi)容進(jìn)行存儲(chǔ),而只是再次存儲(chǔ)一份引用,上面增加的 5 字節(jié)的存儲(chǔ)空間就是新增引用和一些控制信息的空間。反序列化時(shí),恢復(fù)引用關(guān)系,使得程序 中的 t1 和 t2 指向唯一的對(duì)象,二者相等,輸出 true,該存儲(chǔ)規(guī)則極大的節(jié)省了存儲(chǔ)空間。

但需要注意,在上面程序中將//t.a = 10注釋打開,執(zhí)行的結(jié)果也一樣。 原因就是第一次寫入對(duì)象以后,第二次再試圖寫的時(shí)候,虛擬機(jī)根據(jù)引用關(guān)系知道已經(jīng)有一個(gè)相同對(duì)象已經(jīng)寫入文件,因此只保存第二次寫的引用,所以讀取時(shí),都是第一次保存的對(duì)象。

學(xué)習(xí)Java的同學(xué)注意了!!! 學(xué)習(xí)過程中遇到什么問題或者想獲取學(xué)習(xí)資源的話,歡迎加入Java學(xué)習(xí)交流群,群號(hào)碼:523047986  我們一起學(xué)Java!


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 苗栗县| 天长市| 石阡县| 板桥市| 称多县| 化隆| 沁源县| 化隆| 霍山县| 正阳县| 偃师市| 阳曲县| 潜江市| 类乌齐县| 佳木斯市| 乳山市| 华容县| 丽江市| 特克斯县| 马山县| 登封市| 稻城县| 修文县| 灵山县| 北京市| 巴青县| 扎鲁特旗| 时尚| 黄石市| 日喀则市| 青浦区| 黔江区| 偏关县| 始兴县| 北流市| 建始县| 泗阳县| 汤阴县| 清丰县| 宁夏| 高唐县|