序列化:序列化是将对象转成字节的过程
反序列化:反序列化就是将字节还原为对象的过程
需要序列化的原因:
1)持久化:当我们程序创建一个对象的时候,这个对象的生命周期在程序运行结束,或者线程执行完毕之后,就可能被JVM直接回收,被销毁掉。而当我们希望说某个对象在内存中的某个状态被保留下来,这个时候就需要把当前这个对象序列化成二进制字节,然后保存在硬盘里,当需要用的时候,再反序列化还原这个对象即可。例如:当一个程序并发量非常大,需要创建几十万个对象,而对象保存在内存中,一般我们服务器内存不会那么大,或者说在一个内存不足的情况下,就会出现内存溢出,而如果把这些对象暂存在硬盘里面,则就可以缓解内存的压力。
2)传递信息:当处于不同网络中的进程或者服务在进行消息传递的时候,进程间通信是不认识像Java这种对象的,需要把对象序列化成字节,这样计算机才能识别,然后A进程将对象序列化成字节,B进程收到后,再将这些字节还原回对象,则就需要反序列化。一般这种场景在rpc调用非常常见。
例子
序列化需要实现Serializable接口,当调用ObjectOutPutStream的writeObject()方法就可以将一个Object序列化成字节,而相对应的ObjectInPutStream的readObject()方法则可以将二进制字节反序列化为一个对象
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
transient private String sex;
public Student(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
public class Main {
public static void main(String [] args) throws Exception {
Student student = new Student("yushengfan", 20, "男");
FileOutputStream fileOutputStream = new FileOutputStream("student.txt");
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
// 此处将student对象序列化,并且将结果保存在student.txt里面
outputStream.writeObject(student);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("student.txt"));
// 此处从student.txt读取字节信息,并且反序列化为对象
Student student1 = (Student) inputStream.readObject();
System.out.println(student1);
}
}
输出:
Student{name='yushengfan', age=20, sex='null'}
Process finished with exit code 0
可以看到,我们新建的时候,sex是初始化为"男",但是反序列化回来的时候,结果却是null,原因是由于上面的sex字段是用transient修饰,该修饰是代表,序列化的时候,屏蔽对该变量序列化。如果把transient修饰去掉,实现Serializable接口的时候,则会采取默认策略,即该对象的所有成员都序列化。
如:
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String sex;
public Student(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
运行:
public class Main {
public static void main(String [] args) throws Exception {
Student student = new Student("yushengfan", 20, "男");
FileOutputStream fileOutputStream = new FileOutputStream("student.txt");
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
// 此处将student对象序列化,并且将结果保存在student.txt里面
outputStream.writeObject(student);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("student.txt"));
// 此处从student.txt读取字节信息,并且反序列化为对象
Student student1 = (Student) inputStream.readObject();
System.out.println(student1);
}
}
结果为:
Student{name='yushengfan', age=20, sex='男'}
Process finished with exit code 0
自定义序列化
上面的例子是实现Serialization接口,该接口就是采取默认策略,将所有非transient修饰的成员变量全都序列化,而官方也为我们提供了自定义序列化的策略,就是实现Externalizable接口
public class Student implements Externalizable {
private String name;
private int age;
private String sex;
// 实现Externalizable 接口的时候,一定需要默认构造函数,否则会报错
public Student() {}
public Student(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 此处只对成员变量name和age进行序列化
out.writeObject(name);
out.write(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 此处只对name和age属性进行反序列化
this.name = (String) in.readObject();
this.age = in.read();
}
}
运行:
public class Main {
public static void main(String [] args) throws Exception {
Student student = new Student("yushengfan", 20, "男");
FileOutputStream fileOutputStream = new FileOutputStream("student.txt");
ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
outputStream.writeObject(student);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("student.txt"));
Student student1 = (Student) inputStream.readObject();
System.out.println(student1);
}
}
结果:
Student{name='yushengfan', age=20, sex='null'}
Process finished with exit code 0
此处可以看到,虽然没有用transient修饰变量,但是通过自定义序列化,同意也可以实现transient的功能;