一个标示性接口,接口中没有定义任何的方法或字段,仅用于标示可序列化的语义。
序列化时只对对象的状态进行保存,而无论对象的方法;序列化前和序列化后的对象的关系为深拷贝。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(); } } }