本文是基于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 = 小明