一個標示性接口,接口中沒有定義任何的方法或字段,僅用於標示可序列化的語義。
序列化時只對對象的狀態進行保存,而無論對象的方法;序列化前和序列化後的對象的關係爲深拷貝。java
序列化使用一個 hash,該 hash 是根據給定源文件中幾乎全部東西(類路徑、 方法名稱、字段名稱、字段類型、訪問修改方法等) 經過運行 JDK serialver
命令計算出的,反序列化時將該 hash 值與序列化流中的 hash 值相比較,serialVersionUID 相同纔可以被序列化!算法
若是serialVersionUID類中沒有指定,JVM將從新計算出serialVersionUID; 若是類幾乎全部東西都相同仍然可以反序列化,不然不能。函數
java.io.InvalidClassException: com.noob.Person; local class incompatible: stream classdesc serialVersionUID = 7763748706987261198, local class serialVersionUID = 1279018472691830503 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1829) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422) at com.noob.TestSerializable.read(TestSerializable.java:41) at com.noob.TestSerializable.main(TestSerializable.java:18)
若是在反序列化前修改了類路徑(或者JVM沒有加載到原有的類)報錯:測試
eg. 修改了Person的類路徑 由 com.noob 改成 com.noob.athis
java.lang.ClassNotFoundException: com.noob.Person at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:677) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1819) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422) at com.noob.a.TestSerializable.read(TestSerializable.java:41) at com.noob.a.TestSerializable.main(TestSerializable.java:18)
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import lombok.Getter; import lombok.Setter; public class TestSerializable { public static void main(String[] args) { try { /* 深複製 */ ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream readIn = new ObjectOutputStream(bo); A testA = new A("a"); testA.setB("b"); testA.setC("c"); testA.setD("d"); readIn.writeObject(testA); ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream writeOut = new ObjectInputStream(bi); B b = B.class.cast(writeOut.readObject()); // 反序列化對象類型是序列化對象的父類或自己 System.out.println(b); } catch (Exception e) { e.printStackTrace(); } } } @Getter @Setter class A extends B implements Serializable { public A(String a) { super(a); } private static final long serialVersionUID = 1L; private String d; private String a, b, c; } @Getter @Setter class B implements Serializable { private String a, b, c; public B(String a) { this.a = a; } }
測試發現:加密
在父類沒有實現 Serializable 接口時,虛擬機是不會序列化父對象的,而一個 Java 對象的構造必須先有父對象,纔有子對象,反序列化也不例外。因此反序列化時,爲了構造父對象,只能調用父類的無參構造函數做爲默認的父對象。所以當取父對象的變量值時,它的值是調用父類無參構造函數後的值。spa
若是考慮到這種序列化的狀況,在父類無參構造函數中對變量進行初始化,不然的話,父類變量值都是默認聲明的值。.net
Transient 關鍵字的做用是控制變量的序列化,在變量聲明前加上該關鍵字,能夠阻止該變量被序列化到文件中,在被反序列化後,transient 變量的值被設爲初始值。
靜態變量、成員方法、聲明transient的變量 都不能被序列化和反序列化!3d
eg. 使用 Transient 關鍵字可使得字段不被序列化,那麼還有別的方法嗎?
根據父類對象序列化的規則,咱們能夠將不須要被序列化的字段抽取出來放到父類中,子類實現 Serializable 接口,父類不實現,根據父類序列化規則,父類的字段數據將不被序列化,造成類圖:
code
上圖中能夠看出,attr一、attr二、attr三、attr5 都不會被序列化,放在父類中的好處在於當有另一個 Child 類時,attr一、attr二、attr3 依然不會被序列化,不用重複抒寫 transient,代碼簡潔。
即便代碼有必定的變化,可是serialVersionUID相同,仍然能夠被反序列化。當出現新字段時會被設爲缺省值。
eg. 將原有的Person寫入到文件中。再修改Person類:staS改成非 static,firstName 改成非 transient , 增長屬性addField。(static與transient 有或無 可互相轉換,經測試都不能被正確序列化和反序列化)
package com.noob; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import com.zhongan.fcp.pre.allin.common.utils.JSONUtils; public class TestSerializable { public static void main(String[] args) { /* write(); */ read(); } private static void write() { /* try { Person ted1 = new Person("firstName", "lastName", 39); FileOutputStream fos = new FileOutputStream("tempdata.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(ted1); oos.close(); System.out.println("---serialize obj end.----"); } catch (Exception ex) { ex.printStackTrace(); }*/ } private static void read() { try { FileInputStream fis = new FileInputStream("D:/tempdata.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Person ted2 = (Person) ois.readObject(); ois.close(); System.out.println(JSONUtils.toFormatJsonString(ted2)); // Clean up the file new File("tempdata.ser").delete(); } catch (Exception ex) { ex.printStackTrace(); } } } @Data @AllArgsConstructor @NoArgsConstructor class Person implements java.io.Serializable { private static final long serialVersionUID = -5941751315700344441L; private/**static**/String staS = "xxx"; // static 改成非 static private/**transient**/String firstName; // transient 改成非 transient private String lastName; private int age; private String addField; // 增長屬性 }
一個類要能被序列化,該類中的全部引用對象也必須是能夠被序列化的。
不然整個序列化操做將會失敗,而且會拋出一個NotSerializableException,除非將不可序列化的引用標記爲transient。
@Data @AllArgsConstructor @NoArgsConstructor class Person implements java.io.Serializable { /** * */ private static final long serialVersionUID = -5941751315700344441L; private static String staS = "xxx"; // static 改成非 static private transient String firstName; // transient 改成非 transient private String lastName; private int age; private final Attribute attribute = new Attribute(); } @Data class Attribute { private String lock; }
java.io.NotSerializableException: com.noob.Attribute at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at com.noob.TestSerializable.write(TestSerializable.java:26) at com.noob.TestSerializable.main(TestSerializable.java:17) java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.noob.Attribute at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1539) at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2231) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2155) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2013) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422) at com.noob.TestSerializable.read(TestSerializable.java:36) at com.noob.TestSerializable.main(TestSerializable.java:18) Caused by: java.io.NotSerializableException: com.noob.Attribute at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at com.noob.TestSerializable.write(TestSerializable.java:26) at com.noob.TestSerializable.main(TestSerializable.java:17)
若是使用序列化機制向文件中寫入了多個對象,在反序列化時,須要按實際寫入的順序讀取。
JAVA的序列化機制採用了一種特殊的算法來保證序列化對象的關係:
全部保存到磁盤中的對象都有一個序列化編號。當程序試圖序列化一個對象時,會先檢查該對象是否已經被序列化過,只有該對象(在本次虛擬機中)從未被序列化,系統纔會將該對象轉換成字節序列並輸出。若是對象已經被序列化,程序將直接輸出一個序列化編號,而不是從新序列化。
package com.noob; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; public class TestSerializable { public static void main(String[] args) { try { Person person1 = new Person(1000); FileOutputStream fos = new FileOutputStream("D:/tempdata.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(person1); oos.flush(); //可選 System.out.println(String.format("第一次將person1寫入後的長度: %s", new File("D:/tempdata.ser").length())); person1.setAge(5555); //修改屬性值 oos.writeObject(person1); System.out.println(String.format("再次將person1寫入後的長度: %s", new File("D:/tempdata.ser").length())); Person person2 = new Person(1000); oos.writeObject(person2); oos.close(); System.out.println(String.format("初始化新person2寫入後的長度:%s ", new File("D:/tempdata.ser").length())); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/tempdata.ser")); System.out.println("---deserialization obj begin.----"); Person deserialization_person1 = (Person) ois.readObject(); System.out.println("deserialization_person1的age: " + deserialization_person1.getAge()); System.out.println("deserialization_person1的內存: " + deserialization_person1); Person deserialization_person2 = (Person) ois.readObject(); System.out.println("deserialization_person2的age: " + deserialization_person2.getAge()); System.out.println("deserialization_person2的內存: " + deserialization_person2); System.out.println(String.format("deserialization_person1與deserialization_person2是否一致:%s ", deserialization_person1 == deserialization_person2)); Person deserialization_person3 = (Person) ois.readObject(); System.out.println("deserialization_person3的內存: " + deserialization_person3); System.out.println(String.format("deserialization_person1與deserialization_person3是否一致:%s ", deserialization_person1 == deserialization_person3)); } catch (Exception ex) { ex.printStackTrace(); } } } @Getter @Setter @AllArgsConstructor class Person implements java.io.Serializable { /** * */ private static final long serialVersionUID = -5941751315700344441L; private int age; }
測試結果發現:
流只能被讀取一次!
eg. 若是序列化一個對象,反序列化時屢次readObject,報錯
try { Person person = new Person(1000); FileOutputStream fos = new FileOutputStream("D:/tempdata.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(person); oos.close(); FileInputStream fis = new FileInputStream("D:/tempdata.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Person person2 = (Person) ois.readObject(); Person person3 = (Person) ois.readObject(); ois.close(); } catch (Exception ex) { ex.printStackTrace(); }
eg. 有兩個Teacher對象,它們的Student實例變量都引用了同一個Person對象,並且該Person對象還另一個引用變量引用它。以下圖所示:
這裏有三個對象per、t一、t2,若是都被序列化,會存在這樣一個問題,在序列化t1的時候,會隱式的序列化person對象。在序列化t2的時候,也會隱式的序列化person對象。在序列化per的時候,會顯式的序列化person對象。因此在反序列化的時候,會獲得三個person對象,這樣就會形成t一、t2所引用的person對象不是同一個。顯然,這並不符合圖中所展現的關係,也違背了java序列化的初衷。
package com.noob; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import lombok.AllArgsConstructor; import lombok.Getter; public class TestSerializable { public static void main(String[] args) { write(); read(); } private static void write() { try { Person person = new Person(1000); Student student = new Student("孫悟空", person); Teacher teacher1 = new Teacher("唐僧", student); Teacher teacher2 = new Teacher("菩提老祖", student); FileOutputStream fos = new FileOutputStream("D:/tempdata.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(person); oos.flush(); // 可選 oos.writeObject(student); oos.flush(); oos.writeObject(teacher1); oos.flush(); oos.writeObject(teacher2); oos.close(); } catch (Exception ex) { ex.printStackTrace(); } } private static void read() { try { FileInputStream fis = new FileInputStream("D:/tempdata.ser"); ObjectInputStream ois = new ObjectInputStream(fis); System.out.println("---deserialization obj begin.----"); Person person = (Person) ois.readObject(); System.out.println("person的內存: " + person); Student student = (Student) ois.readObject(); System.out.println("student中person內存: " + student.getPerson()); System.out.println(String.format("student中person與直接反序列化的person是否一致:%s", student.getPerson() == person)); System.out.println("-------------------------------------------"); System.out.println("student的內存: " + student); Teacher teacher1 = (Teacher) ois.readObject(); System.out.println("teacher1中student內存: " + teacher1.getStudent()); System.out .println(String.format("teacher1中student與直接反序列化的student是否一致:%s", teacher1.getStudent() == student)); Teacher teacher2 = (Teacher) ois.readObject(); System.out.println("teacher2中student內存: " + teacher2.getStudent()); System.out .println(String.format("teacher2中student與直接反序列化的student是否一致:%s", teacher2.getStudent() == student)); ois.close(); // Clean up the file new File("tempdata.ser").delete(); } catch (Exception ex) { ex.printStackTrace(); } } } /** * 此處沒有getter/setter 佐證序列和反序列化與此無關 */ @AllArgsConstructor class Person implements java.io.Serializable { /** * */ private static final long serialVersionUID = -5941751315700344441L; private int age; } @Getter @AllArgsConstructor class Student implements java.io.Serializable { /** * */ private static final long serialVersionUID = 1L; private String name; private Person person; } @Getter @AllArgsConstructor class Teacher implements java.io.Serializable { /** * */ private static final long serialVersionUID = 1L; private String name; private Student student; }
測試結論: 在反序列化後仍舊會保持與序列化前對象間的引用關係!
在序列化過程當中,虛擬機會試圖調用對象類裏的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化,若是沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
package com.noob; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectInputStream.GetField; import java.io.ObjectOutputStream; import java.io.ObjectOutputStream.PutField; public class Test implements java.io.Serializable { /** * */ private static final long serialVersionUID = 1L; private String password = "origin_password"; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } private void writeObject(ObjectOutputStream out) { try { 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 { GetField readFields = in.readFields(); Object object = readFields.get("password", ""); System.out.println("要解密的字符串:" + object.toString()); password = "origin_password";//此處能夠經過私鑰進行解密 } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) { try { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/result.obj")); out.writeObject(new Test()); out.close(); ObjectInputStream oin = new ObjectInputStream(new FileInputStream("D:/result.obj")); Test t = (Test) oin.readObject(); System.out.println("解密後的字符串:" + t.getPassword()); oin.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }