亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Java > 正文

Java Serializable系列化與反系列化

2019-11-06 06:18:36
字體:
來源:轉載
供稿:網友

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

【引言】

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

【系列化serialVersionUID問題】

    在Java系列化與反系列化中,虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 PRivate static final long serialVersionUID = 1L),如果serialVersionUID不同,你將得到一個java.io.InvalidClassException,看如下代碼:[java] view plain copypackage wen.hui.test.serializable;    import java.io.Serializable;    /**  * serializable測試  *  * @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、直接運行Test1的main方法,運行正確;2、先將Test1的main方法中的toRead(fileName)注釋,把類A中的serialVersionUID 值改為1,運行Test1;然后在代開toRead(fileName),將toWrite(fileName)注釋,同時將類A中的serialVersionUID 值改為2;運行Test1,發現拋出異常,表明如果serialVersionUID不同,即使兩個“完全”相同的類也無法反序列化。[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 下提供了兩種生成策略,一個是固定的 1L,一個是隨機生成一個不重復的 long 類型數據(實際上是使用 JDK 工具生成),在這里有一個建議,如果沒有特殊需求,就是用默認的 1L 就可以,這樣可以確保代碼一致時反序列化成功。那么隨機生成的序列化 ID 有什么作用呢,有些時候,通過改變序列化 ID 可以用來限制某些用戶的使用。如Facade模式中,Client 端通過 Fa?ade Object 才可以與業務邏輯對象進行交互。而客戶端的 Fa?ade Object 不能直接由 Client 生成,而是需要 Server 端生成,然后序列化后通過網絡將二進制對象數據傳給 Client,Client 負責反序列化得到 Fa?ade 對象。該模式可以使得 Client 端程序的使用需要服務器端的許可,同時 Client 端和服務器端的 Fa?ade Object 類需要保持一致。當服務器端想要進行版本更新時,只要將服務器端的 Fa?ade Object 類的序列化 ID 再次生成,當 Client 端反序列化 Fa?ade Object 就會失敗,也就是強制 Client 端從服務器端獲取最新程序。

【靜態變量序列】

直接看代碼:[java] view plain copypackage wen.hui.test.serializable;    import java.io.Serializable;    /**  * serializable測試  *  * @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;    /**  * 序列化保存的是對象的狀態,靜態變量屬于類的狀態,不會被序列化  * @author whwang  * 2011-12-1 下午10:12:06  */  public class Test2 {        public static void main(String[] args) {          try {              // 初始時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類的靜態字段staticVar初始化值為10,在Teste2的main方法中,將A類的一個實例系列化到硬盤,然后修改靜態字段staticVar = 100,接著反系列化剛系列化的對象,輸出”該對象的“staticVar的值。輸出的是 100 還是 10 呢?結果輸出是100,之所以打印 100 的原因在于序列化時,并不保存靜態變量,這其實比較容易理解,序列化保存的是對象的狀態,靜態變量屬于類的狀態,因此 序列化并不保存靜態變量。

【父類的序列化與 Transient 關鍵字】

情境:一個子類實現了 Serializable 接口,它的父類都沒有實現 Serializable 接口,序列化該子類對象,然后反序列化后輸出父類定義的某變量的數值,該變量數值與序列化時的數值不同。解決:要想將父類對象也序列化,就需要讓父類也實現Serializable 接口。如果父類不實現的話的,就 需要有默認的無參的構造函數。在父類沒有實現 Serializable 接口時,虛擬機是不會序列化父對象的,而一個 Java 對象的構造必須先有父對象,才有子對象,反序列化也不例外。所以反序列化時,為了構造父對象,只能調用父類的無參構造函數作為默認的父對象。因此當我們取父對象的變量值時,它的值是調用父類無參構造函數后的值。如果你考慮到這種序列化的情況,在父類無參構造函數中對變量進行初始化,否則的話,父類變量值都是默認聲明的值,如 int 型的默認是 0,string 型的默認是 null。Transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 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;    /**  * 如果父類沒有實現Serializable,那么父類不會被系列化,當反系列化子類時  * 會調用父類無參的構造方法。  * @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();          }      }    }  運行Test3的main方法,結果輸出0, 100, 0, -100;即在子類的構造方法中對父類的成員變量的初始化沒有被系列化;而反系列化時,則是調用父類的無參構造方法實例化父類。

【對敏感字段加密】

情境:服務器端給客戶端發送序列化對象數據,對象中有一些數據是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時,才可以對密碼進行讀取,這樣可以一定程度保證序列化對象的數據安全。解決:在序列化過程中,虛擬機會試圖調用對象類里的 writeObject(ObjectOutputStread out) 和 readObject(ObjectInputStread in) 方法(通過反射機制),進行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態改變序列化的數值。基于這個原理,可以在實際應用中得到使用,用于敏感字段的加密工作,如:[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: 加密測試。  * @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();          }      }        }  在系列化之前,將密碼字段加密,然后系列化到硬盤,在反系列化時,通過類D中的readObject(ObjectInputStream in)做解密操作,確保了數據的安全。如:RMI 技術是完全基于 Java 序列化技術的,服務器端接口調用所需要的參數對象來至于客戶端,它們通過網絡相互傳輸。這就涉及 RMI 的安全傳輸的問題。一些敏感的字段,如用戶名密碼(用戶登錄時需要對密碼進行傳輸),我們希望對其進行加密,這時,就可以采用本節介紹的方法在客戶端對密碼進行加密,服務器端進行解密,確保數據傳輸的安全性。

【序列化存儲規則】

請看如下代碼:[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();              // 試圖將對象兩次寫入文件              out.writeObject(t);              out.flush();              System.err.println("加入第一個類:" + new File("obj").length());              //t.a = 10;              out.writeObject(t);              out.close();              System.err.println("加入第二個類:" + new File("obj").length());                ObjectInputStream oin = new ObjectInputStream(new FileInputStream(                      "obj"));              // 從文件依次讀出兩個文件              A t1 = (A) oin.readObject();              A t2 = (A) oin.readObject();              oin.close();                // 判斷兩個引用是否指向同一個對象              System.err.println(t1 == t2);                        } catch (FileNotFoundException e) {              e.printStackTrace();          } catch (IOException e) {              e.printStackTrace();          } catch (ClassNotFoundException e) {              e.printStackTrace();          }      }    }  對同一對象兩次寫入文件,打印出寫入一次對象后的存儲大小和寫入兩次后的存儲大小,然后從文件中反序列化出兩個對象,比較這兩個對象是否為同一對象。一般的思維是,兩次寫入對象,文件大小會變為兩倍的大小,反序列化時,由于從文件讀取,生成了兩個對象,判斷相等時應該是輸入 false 才對。但實際結果是:第二次寫入對象時文件只增加了 5 字節,并且兩個對象是相等的,這是為什么呢?解答:Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時(根據包名+類名),并不會再將對象的內容進行存儲,而只是再次存儲一份引用,上面增加的 5 字節的存儲空間就是新增引用和一些控制信息的空間。反序列化時,恢復引用關系,使得程序 中的 t1 和 t2 指向唯一的對象,二者相等,輸出 true,該存儲規則極大的節省了存儲空間。

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

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


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美日本在线视频中文字字幕| 中文日韩在线视频| 欧美激情第一页xxx| 国产精品第8页| 欧美日本啪啪无遮挡网站| 2019中文在线观看| 亚洲成人精品在线| 国产精品美女久久久免费| 亚洲欧美日韩成人| 亚洲欧美激情精品一区二区| 欧美成人sm免费视频| 成人午夜一级二级三级| 日韩欧美国产成人| 日韩中文字幕在线免费观看| 欧美大片在线影院| 日韩在线视频网| 亚洲www在线| 欧美做受高潮电影o| 久久久999精品免费| 色偷偷亚洲男人天堂| 国产精品视频专区| 国产精品白嫩初高中害羞小美女| 久久99精品视频一区97| 亚洲国产日韩欧美综合久久| 欧美性猛交xxxx久久久| 日韩欧美一区二区在线| 亚洲女人天堂网| 日韩精品黄色网| 久久精品人人爽| 国产精品人成电影| 欧美激情视频在线免费观看 欧美视频免费一| 欧美日本精品在线| 亚洲bt天天射| 57pao成人永久免费视频| 亚洲国产精品va在线观看黑人| 国产精品电影网站| 国产成人综合精品在线| 久久久久国产精品免费| 亚洲欧美日韩直播| 国产精品久久久久久久久久久久| 啪一啪鲁一鲁2019在线视频| 91香蕉亚洲精品| 亚洲一级免费视频| 91av视频在线观看| 精品久久香蕉国产线看观看gif| 国产精品美乳在线观看| 精品欧美国产一区二区三区| 北条麻妃99精品青青久久| 亚洲va欧美va国产综合剧情| 一本色道久久88综合亚洲精品ⅰ| 亚洲国产婷婷香蕉久久久久久| 日韩激情视频在线| 亚洲国产精品久久久久久| 欧美另类暴力丝袜| 国产精品久久久久免费a∨| 国产精品网红直播| 国产美女搞久久| 国产精品高潮呻吟久久av黑人| 亚洲欧美国内爽妇网| 欧美国产亚洲视频| 美女少妇精品视频| 亚洲成人精品在线| 国产精品99久久久久久久久久久久| 欧美日韩精品国产| 亚洲综合在线播放| 日韩高清不卡av| 亚洲电影免费观看| 亚洲精品自在久久| 91在线视频一区| 一区二区三区回区在观看免费视频| 日韩av影视综合网| 91国产高清在线| 亚洲视频日韩精品| 日韩亚洲国产中文字幕| 亚洲欧美一区二区精品久久久| 国产精品国产三级国产专播精品人| 亚洲色图国产精品| 韩曰欧美视频免费观看| 国产精品久久久久久久久久三级| 日韩最新av在线| 久久久久久久久久久免费精品| 疯狂欧美牲乱大交777| 亚洲欧美一区二区三区在线| 亚洲精品国产美女| 茄子视频成人在线| 精品久久久久久久久中文字幕| 日韩av免费网站| 国产精品久久久久久超碰| 97**国产露脸精品国产| 久久久国产视频| 国产成人avxxxxx在线看| 成人福利免费观看| 91tv亚洲精品香蕉国产一区7ujn| 欧美老少做受xxxx高潮| 中文字幕精品一区二区精品| 久久99精品久久久久久琪琪| 亚洲一区美女视频在线观看免费| 国产精品一区二区三区成人| 精品电影在线观看| 欧美天天综合色影久久精品| 国产日韩av在线播放| 亚洲一区二区在线| 日韩亚洲第一页| 亚洲精品综合久久中文字幕| 精品亚洲va在线va天堂资源站| 欧美理论片在线观看| 7777精品久久久久久| 欧美激情中文字幕在线| 国产91精品视频在线观看| 国产精品视频最多的网站| 国产成人免费av| www国产91| 日韩欧美综合在线视频| 欧美激情亚洲另类| 欧美极品少妇xxxxⅹ免费视频| 国产精品视频一| 国产精品久久久久久久av大片| 欧美第一淫aaasss性| 国产伦精品一区二区三区精品视频| 黑人狂躁日本妞一区二区三区| 日韩av电影在线播放| 久久精品国产一区| 久久久国产精品x99av| 97精品久久久中文字幕免费| 日韩中文字幕国产| 精品日韩视频在线观看| 久久久精品国产一区二区| 91欧美精品午夜性色福利在线| 97国产精品视频人人做人人爱| 亚洲激情视频在线| 18久久久久久| 伊人久久免费视频| 亚洲精品一区二三区不卡| 国产精品入口夜色视频大尺度| 国产精品久久婷婷六月丁香| 亚洲欧美一区二区三区在线| 成人免费自拍视频| 久久亚洲精品中文字幕冲田杏梨| 久久精品99久久久久久久久| 欧美中文字幕视频在线观看| 538国产精品一区二区免费视频| 亚洲深夜福利在线| 国产日韩一区在线| 午夜精品一区二区三区视频免费看| 国产精品欧美日韩久久| 国产视频一区在线| 欧美做受高潮1| 欧美日韩国产精品| 欧洲午夜精品久久久| 欧美一级视频在线观看| 久热精品视频在线| 中文字幕亚洲欧美日韩在线不卡| 在线免费看av不卡| 成人在线精品视频| 97av在线视频| 国产91在线播放九色快色| 欧美另类在线播放| 欧美国产欧美亚洲国产日韩mv天天看完整| 亚洲欧美国产一本综合首页| 午夜精品国产精品大乳美女| 亚洲欧美制服另类日韩| 国产精品成人国产乱一区| 国产综合视频在线观看| 国产欧美va欧美va香蕉在|