Java 知识点:序列化

java

首先明确一点:默认的序列化方法速度很慢,因为需要对整个对象和他的类都进行保存,因此我们建议自定义序列化格式。

用途ObjectInputStreamObjectOutputStream
整数readInt()writeInt(int)
浮点数readDouble()writeDouble(double)
字符串readUTF()writeUTF(String)
字节数组read(byte[] buf, int off,int length)write(byte[])
对象readObject()writeObject(Object)

哪些数据不会被序列化

  1. 被标记为 transient 的域。
  2. 静态变量。

查看某个类的SerialVersionUID

如果类A实现了Serializable接口,则可以使用Java 提供的serialver命令: serialver  A 。

SerialVersionUID变量的作用

场景:你在公司开发一个类(记为LogManager,用来管理日志,拥有变量Date date,String description,我们使用序列化保存日志信息),并且已经上线使用(已经积累了一些数据),随着时间的推移,你发现你这个类设计的不够完善,因此你需要添加一个实例变量 int errorID,那么你如果按照一般反序列化方法(readObject),则会抛出:InvalidClassException。那么怎么能够成功将原始的数据转换成新版本的LogManager对象呢?

解决:

  1. 使用 Java 提供的serialver命令: serialver  LogManager 计算出原始LogManager的 serialVersionUID(比如为123L)。
  2. 在新版本的LogManager中添加:  static final long serialVersionUID = 123L;

目的:保持不同版本类的序列化的兼容性。

序列化类A(类A继承自类B),但是类B不可序列化,怎么办?

默认情况:

  1. 先将类A的实例变量全部还原。
  2. 因为类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 }

View Code

利用序列化实现深层复制

 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 }

View Code

序列化包含别人开发过的不可序列化的类

问题:如果你想要开发一个 House 类,此时别人已经开发好的 Furniture 类(Furniture 类中有实例变量int size,没有类Furniture的源代码,只有class文件,且类Furniture不是可序列化的)。因为House HAS-A Furniture,因此需要组合,因此在House类中需要声明一个Furniture的实例变量,但是Furniture并不能实例化,如果按照一般的方法,则会抛“NoSerializableException”。

解决:

  1. 将Furniture实例变量设为transient。
  2. 在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 }

View Code

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 }

View Code

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()时,过程如下:

  1. 假设读取类A的对象,则首先调用类A的无参构造函数(只有在实现Externalizable接口时才会调用无参构造函数),因此我们必须要实现这个构造函数。
  2. 因为类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 }

View Code

Reference

[1]http://www.ibm.com/developerworks/cn/java/j-5things1/
[2]Java核心技术(第7版) P617~637

以上是 Java 知识点:序列化 的全部内容, 来源链接: utcz.com/z/389548.html

回到顶部