Java 知识点:序列化
首先明确一点:默认的序列化方法速度很慢,因为需要对整个对象和他的类都进行保存,因此我们建议自定义序列化格式。
ObjectInputStream和ObjectOutputStream
用途 | ObjectInputStream | ObjectOutputStream |
整数 | readInt() | writeInt(int) |
浮点数 | readDouble() | writeDouble(double) |
字符串 | readUTF() | writeUTF(String) |
字节数组 | read(byte[] buf, int off,int length) | write(byte[]) |
对象 | readObject() | writeObject(Object) |
哪些数据不会被序列化
- 被标记为 transient 的域。
- 静态变量。
查看某个类的SerialVersionUID
如果类A实现了Serializable接口,则可以使用Java 提供的serialver命令: serialver A 。
SerialVersionUID变量的作用
场景:你在公司开发一个类(记为LogManager,用来管理日志,拥有变量Date date,String description,我们使用序列化保存日志信息),并且已经上线使用(已经积累了一些数据),随着时间的推移,你发现你这个类设计的不够完善,因此你需要添加一个实例变量 int errorID,那么你如果按照一般反序列化方法(readObject),则会抛出:InvalidClassException。那么怎么能够成功将原始的数据转换成新版本的LogManager对象呢?
解决:
- 使用 Java 提供的serialver命令: serialver LogManager 计算出原始LogManager的 serialVersionUID(比如为123L)。
- 在新版本的LogManager中添加: static final long serialVersionUID = 123L;
目的:保持不同版本类的序列化的兼容性。
序列化类A(类A继承自类B),但是类B不可序列化,怎么办?
默认情况:
- 先将类A的实例变量全部还原。
- 因为类A继承类B,因此类A的对象也会有类B的实例变量,对于类B的实例变量,调用类B的默认无参构造函数初始化类B的实例变量。(一定要定义超类无参构造函数,不然会抛 no valid constructor)
解决:自定义readObject和writeObject。
对序列化的数据加密
问题:我们知道,序列化主要用于数据传输,但是序列化的数据是可以反序列化的,因此黑客可以直接把你的数据截下来(比如你的序列化文件为data.txt,用WinHeX打开后,基本就能看到所涉及的类和你传输的数据)。那么怎么样能够加密序列化的数据呢?
解决:通过自定义的序列化方法(在要序列化的对象中实现readObject和writeObject方法)。
- private void writeObject(ObjectOutputStream os)throws IOException // 你如果重写时必须是private的。
- private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException //你如果重写时必须是private的
1 import java.io.*; 2 public class Serialize05 3 { 4 public static void main(String[] args) throws Exception{ 5 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt")); 6 out.writeObject(new Person("admin","abc123")); 7 out.close(); 8 ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt")); 9 Person person = (Person)in.readObject(); 10 System.out.println(person); 11 } 12 } 13 class Person implements Serializable 14 { 15 String name; 16 String password; 17 public Person(String name,String password) 18 { 19 this.name = name; 20 this.password = password; 21 } 22 private void writeObject(ObjectOutputStream os)throws IOException 23 { 24 os.writeUTF(name); 25 byte[] pass = password.getBytes("UTF-8"); 26 for(int i=0;i<pass.length;i++) 27 { 28 pass[i] = (byte)(pass[i] ^ 32); //对密码加密 29 } 30 os.write(pass); 31 } 32 private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException 33 { 34 name = is.readUTF(); 35 byte[] pass = new byte[1024]; 36 int size = is.read(pass,0,1024); 37 for(int i=0;i<pass.length;i++) 38 { 39 pass[i] = (byte)(pass[i] ^ 32); //对密码解密 40 } 41 String password = new String(pass,0,size,"UTF-8"); 42 this.password = password; 43 } 44 public String toString() 45 { 46 return "name="+name+",password="+password; 47 } 48 }
利用序列化实现深层复制
1 /* 2 用序列化实现深层复制 3 */ 4 import java.io.*; 5 public class Serialize09 6 { 7 public static void main(String[] args) throws Exception{ 8 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 9 ObjectOutputStream out = new ObjectOutputStream(bout); 10 Person f1 = new Person("f1",null); 11 Person p1 = new Person("u1",f1); 12 out.writeObject(p1); 13 byte[] b = bout.toByteArray(); 14 ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(b)); 15 Person p2 = (Person)in.readObject(); 16 p2.friend.name = "f2"; 17 System.out.println(p1.friend.name); //输出:f1.虽然p2的朋友名字改变了,但是p1的朋友没改。 18 19 } 20 } 21 class Person implements Serializable 22 { 23 String name; 24 Person friend; 25 public Person(String name,Person friend) 26 { 27 this.name = name; 28 this.friend = friend; 29 } 30 }
序列化包含别人开发过的不可序列化的类
问题:如果你想要开发一个 House 类,此时别人已经开发好的 Furniture 类(Furniture 类中有实例变量int size,没有类Furniture的源代码,只有class文件,且类Furniture不是可序列化的)。因为House HAS-A Furniture,因此需要组合,因此在House类中需要声明一个Furniture的实例变量,但是Furniture并不能实例化,如果按照一般的方法,则会抛“NoSerializableException”。
解决:
- 将Furniture实例变量设为transient。
- 在House类中实现readObject和writeObject方法。
实现方法:
- private void writeObject(ObjectOutputStream os)throws IOException
- private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException
1 import java.io.*; 2 public class Serialize03 3 { 4 public static void main(String[] args) throws Exception { 5 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt")); 6 out.writeObject(new A(new B(10),100)); 7 out.close(); 8 ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt")); 9 A a = (A)in.readObject(); 10 System.out.println(a); //输出:b=10,a=100 11 } 12 } 13 class B //别人已经开发好的类,且假设看不到B的源代码 14 { 15 int bb; 16 public B(int bb) 17 { 18 this.bb = bb; 19 } 20 } 21 class A implements Serializable 22 { 23 transient B b; 24 int a; 25 public A(B b,int a) 26 { 27 this.b = b; 28 this.a = a; 29 } 30 public String toString() 31 { 32 return "b="+b.bb+",a="+a; 33 } 34 private void writeObject(ObjectOutputStream os)throws IOException //手工序列化B b 35 { 36 os.defaultWriteObject(); 37 os.writeInt(b.bb); 38 } 39 private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException 40 { 41 is.defaultReadObject(); 42 b = new B(is.readInt()); 43 } 44 }
writeReplace和readResolve方法
如果有一个类A、类AProxy,如果想要实现以下任务:当 writeObject(A a) 时,实际写入的是AProxy对象(代理类),则可以使用readResolve和writeReplace。
- 在类A中实现writeReplace方法(当ObjectOutputStream.writeObject(A)时调用该方法)
- Object writeReplace() throws ObjectStreamException。
- 在类AProxy中实现 readResolve 方法(当ObjectInputStream.readObject(A)时调用该方法)
- Object readResolve() throws ObjectStreamException。
1 import java.io.*; 2 public class Serialize07 3 { 4 public static void main(String[] args) throws Exception{ 5 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt")); 6 out.writeObject(new Person("admin",20)); 7 out.close(); 8 ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt")); 9 Person person = (Person)in.readObject(); 10 System.out.println(person); 11 } 12 } 13 14 class PersonProxy implements Serializable 15 { 16 String data; 17 public PersonProxy(Person person) 18 { 19 data = person.name+","+person.age; 20 } 21 private Object readResolve() throws ObjectStreamException 22 { 23 System.out.println("调用了readResolve方法"); 24 String name = data.split(",")[0]; 25 int age = Integer.parseInt(data.split(",")[1]); 26 Person person = new Person(name,age); 27 return person; 28 } 29 } 30 class Person implements Serializable 31 { 32 String name; 33 int age; 34 public Person(String name,int age) 35 { 36 this.name = name; 37 this.age = age; 38 } 39 private Object writeReplace() throws ObjectStreamException 40 { 41 System.out.println("调用了writeReplace方法"); 42 return new PersonProxy(this); 43 } 44 public String toString() 45 { 46 return "name=" + name + ",age=" + age; 47 } 48 }
Externalizable接口
用途:自定义流格式,比如类A没有实现序列化,而类B继承类A,而类B需要负责包括超类数据的保存和恢复。
实现方法:
- void writeExternal(ObjectOutput out) throws IOException
- void readExternal(ObjectInput in) throws IOException,ClassNotFoundException
从API中可以看出,Externalizable接口实现了Serializable接口。
如果一个类A实现了Externalizable接口,则
- ObjectInputStream.readObject()时会调用readExternal(),而不是readObject()
- ObjectOutputStream.writeObject()时会调用writeExternal(),而不是writeObject()
当ObjectInputStream.readObject()时,过程如下:
- 假设读取类A的对象,则首先调用类A的无参构造函数(只有在实现Externalizable接口时才会调用无参构造函数),因此我们必须要实现这个构造函数。
- 因为类A实现了Externalizable接口,则调用readExternal()。
1 import java.io.*; 2 public class Serialize06 3 { 4 public static void main(String[] args) throws Exception{ 5 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt")); 6 out.writeObject(new Employee("admin",20,1000)); 7 out.close(); 8 ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt")); 9 Employee person = (Employee)in.readObject(); 10 System.out.println(person); 11 } 12 } 13 14 class Person 15 { 16 String name; 17 int age; 18 public Person() 19 { 20 } 21 } 22 class Employee extends Person implements Externalizable 23 { 24 transient double salary; 25 public Person() 26 { 27 } 28 public Employee(String name,int age,double salary) 29 { 30 this.name = name; 31 this.age = age; 32 this.salary = salary; 33 } 34 public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException 35 { 36 name = in.readUTF(); 37 age = in.readInt(); 38 salary = in.readDouble(); 39 } 40 public void writeExternal(ObjectOutput out)throws IOException 41 { 42 out.writeUTF(name); 43 out.writeInt(age); 44 out.writeDouble(salary); 45 } 46 public String toString() 47 { 48 return "name=" + name + ",age=" + age + ",salary=" + salary; 49 } 50 }
Reference
[1]http://www.ibm.com/developerworks/cn/java/j-5things1/
[2]Java核心技术(第7版) P617~637