本文是基于Linux环境运行,读者阅读前需要具备一定Linux知识
对象序列化
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其他程序一旦获取到这种二进制流,都可以将这种二进制流恢复成原来的Java对象
对象序列化是将一个Java对象写入IO流,与此对应的是,对象反序列化是从IO流中恢复Java对象,如果需要让某个对象支持序列化机制,必须让其实现如下两个接口:
- Serializable
- Externalizable
代码1-1中,声明一个Person对象,Person对象实现了Serializable接口,将对象序列化到磁盘文件
代码1-1
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; class Person implements Serializable { private String name; private int age; public Person() { super(); System.out.println("Person()构造方法"); } public Person(String name, int age) { super(); System.out.println("Person(String name, int age)构造方法"); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class WriteObject { public static void main(String[] args) { if (args == null || args.length == 0) { throw new RuntimeException("请输入对象序列化路径"); } Person p = new Person("小红", 10); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(args[0])); oos.writeObject(p); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (oos != null) { oos.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
代码1-2位从磁盘文件读取Person对象
代码1-2
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class ReadObject { public static void main(String[] args) { if (args == null || args.length == 0) { throw new RuntimeException("请输入对象序列化路径"); } ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(args[0])); Person p = (Person) ois.readObject(); System.out.println(p); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (ois != null) { ois.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
代码1-1、代码1-2运行结果:
root@lejian:/home/software/.io# touch object root@lejian:/home/software/.io# java WriteObject object Person(String name, int age)构造方法 root@lejian:/home/software/.io# java ReadObject object Person [name=小红, age=10]
需要指出一点的是:Person类虽然声明两个构造器,一个有参数,一个没有参数,但在反序列读取Java对象时,并没有看到程序调用Person任何一个构造器,这表明反序列化机制无须通过构造器来初始化Java对象
对象引用的序列化
如果某各类的属性类型不是基本类型或者String类型,且没有实现可序列化接口,则该类型属性类是不可序列化
代码1-3
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; class Teacher implements Serializable { private String name; private Student student; public Teacher(String name, Student student) { super(); this.name = name; this.student = student; } public String getName() { return name; } public Student getStudent() { return student; } @Override public String toString() { return "Teacher [name=" + name + ", student=" + student + "]"; } } class Student implements Serializable { private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } } public class WriteTeacher { public static void main(String[] args) { if (args == null || args.length == 0) { throw new RuntimeException("请输入对象序列化路径"); } ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(args[0])); Student student = new Student("小明", 15); Teacher teacher1 = new Teacher("王老师", student); Teacher teacher2 = new Teacher("张老师", student); System.out.println("student:" + student); System.out.println("teacher1:" + teacher1); System.out.println("teacher2:" + teacher2); oos.writeObject(teacher1); oos.writeObject(teacher2); oos.writeObject(student); oos.writeObject(teacher2); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (oos != null) { oos.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
代码1-4
import java.io.FileInputStream; import java.io.ObjectInputStream; public class ReadTeacher { public static void main(String[] args) { if (args == null || args.length == 0) { throw new RuntimeException("请输入对象序列化路径"); } ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(args[0])); Teacher teacher1 = (Teacher) ois.readObject(); System.out.println("teacher1:" + teacher1); Teacher teacher2 = (Teacher) ois.readObject(); System.out.println("teacher2:" + teacher2); Student student = (Student) ois.readObject(); System.out.println("student:" + student); Teacher teacher3 = (Teacher) ois.readObject(); System.out.println("teacher3:" + teacher3); System.out.println("teacher2 == teacher3:" + (teacher2 == teacher3)); System.out.println("teacher1.student == teacher2.student:" + (teacher1.getStudent() == teacher2.getStudent())); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
代码1-3、1-4运行结果:
root@lejian:/home/software/.io# touch object root@lejian:/home/software/.io# java WriteTeacher object student:Student [name=小明, age=15] teacher1:Teacher [name=王老师, student=Student [name=小明, age=15]] teacher2:Teacher [name=张老师, student=Student [name=小明, age=15]] root@lejian:/home/software/.io# java ReadTeacher object teacher1:Teacher [name=王老师, student=Student [name=小明, age=15]] teacher2:Teacher [name=张老师, student=Student [name=小明, age=15]] student:Student [name=小明, age=15] teacher3:Teacher [name=张老师, student=Student [name=小明, age=15]] teacher2 == teacher3:true teacher1.student == teacher2.student:true
当程序序列化一个Teacher对象时,如果该Teacher对象持有一个Student对象的引用,为了在反序列化时可以正常恢复该Teacher对象,程序会顺带序列化Teacher对象所引用的Student对象,如果Student对象非空且没有实现Serializable接口,则在序列化时会抛出NotSerializableException异常
代码1-3中,程序先序列化teacher1,则会将teacher1所引用的student对象一起序列化,再序列化teacher2,同样会序列化teacher2所引用的student对象,最后再序列化student对象,这个过程中,程序似乎针对student对象序列化了三次,如果程序对student对象序列化了三次,那么在反序列化时,将会得到三个student对象,teacher1和teacher2所引用的student对象不是同一个,与最初的效果不一致,这将违背Java序列化机制的初衷
为了解决这一问题,Java序列化机制采用了一种特殊的序列化算法,大致如下:
- 所有保存到磁盘中的对象都有一个序列化编号
- 当程序试图序列化一个对象时,程序会先检查该对象是否被序列化过,只有该对象从未在本次虚拟机中被序列化过,系统才会将该对象转换成字节序列并输出
- 如果某个对象被序列化过,程序将直接输出一个序列化编号,而不是重新序列化该对象
由于Java序列化机制使然,如果多次序列化同一个Java对象时,只有第一次序列化才会把该Java对象转换成字节序列并输出,可能会引起一个潜在的问题,当对象的某些属性或状态修改后,再次序列化该对象,程序只是输出前面的序列化编号,而该对象被改变的值却不会同步到输出流中,如代码1-5
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; class Student implements Serializable { private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class SerializeMutable { public static void main(String[] args) { if (args == null || args.length == 0) { throw new RuntimeException("请输入对象序列化路径"); } ObjectOutputStream oos = null; ObjectInputStream ois = null; try { oos = new ObjectOutputStream(new FileOutputStream(args[0])); ois = new ObjectInputStream(new FileInputStream(args[0])); Student student = new Student("小明", 15); oos.writeObject(student); student.setName("小王"); oos.writeObject(student); Student student1 = (Student) ois.readObject(); Student student2 = (Student) ois.readObject(); System.out.println("student1 == student2 : " + (student1 == student2)); System.out.println("student1.name = " + student1.getName()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (oos != null) { oos.close(); } if (ois != null) { ois.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
代码1-5运行结果:
root@lejian:/home/software/.io# touch object root@lejian:/home/software/.io# java SerializeMutable object student1 == student2 : true student1.name = 小明