关于java对象的序列化和反序列化很早就有所了解,但是一直对这个知识是一知半解。最近看了几篇文章,加上自己的思考,对于这个问题有了更深的理解,体会到了温故而知新
一. 序列化和反序列化
- 序列化的多种方式
java中有多种序列化方式,jdk序列化, json序列化, Hessian.....,我平时接触到了jdk序列化和json序列化。
比如一个类实现Serializable接口,表示这个类可以被序列化,这种就是jdk自带的序列化; SpringMvc中使用了Jackson将后台的实体数据序列化成json数据,在前端进行展示,这种是json序列化
序列化: 将对象转换为二进制流的过程
反序列化: 将二进制流恢复为对象的过程
通俗一点讲,就是把对象转换成另一种形式保存。即:将对象中的属性,属性值,属性类型....信息以另一种形式进行保存。
把对象比成雪人,雪人可以拆分成一堆雪。雪更容易运输。后面我们可以再把雪堆成雪人,它的本质是没有改变的,其中包含的内容都是一样的。 - 什么情况下需要序列化
把内存中的java对象持久化到文件中或者数据库
需要通过网络传输数据,如:客户端和服务器交互数据
二. java类序列化
- 序列化常用的API
java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.Serializable
java.io.Externalizable - java.io.Serializable: 一个用于标记的空接口,没有方法或字段,实现了该接口表示,对象可以被序列化
- java.io.Externalizable: 继承了Serializable,其中定义了两个抽象方法writeExternal()和readExternal()。如果实现了Externalizable接口,需要重写两个方法
- java.io.ObjectOutputStream: 表示对象输出流,它的writeObject(Object obj)方法可以将指定的对象序列化为二进制流
- java.io.ObjectInputStream: 表示对象输入流,它的Object readObject()方法,读取二进制流转换为一个对象
//定义一个类实现Serializable
public class Stud implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Stud{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws IOException {
Stud stud = new Stud();
stud.setName("test");
stud.setAge(11);
// 将对象序列化成二进制流保存到文件中,执行代码后可以在test.txt中看到数据,
//只不过数据不是文本内容,看似乱码
FileOutputStream fos = new FileOutputStream("D:\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(stud);
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("D://test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
// 反序列为对象
Stud stud =(Stud) ois.readObject();
System.out.println(stud);
}
需要注意的几个点:
1.如果一个对象要进行序列化,它的成员变量是引用类型,那么这个引用类型也要可序列化,否则程序会报错
2.父类实现了Serializable或者Externalizable,表示可以序列化;子类间接实现序列化接口,也可以被序列化
3.如果子类可以被序列化,但是父类没有实现序列化接口,那么从父类继承的成员变量是不会被序列化的
4.静态变量不会参与序列化;非静态变量使用transient修饰也不会参与序列化,反序列化时会按照其类型的默认值赋值
//定义一个类实现Externalizable,必须重写两个方法
public class Stud implements Externalizable {
private static final long serialVersionUID = -6541501724579511412L;
private String address;
private String name;
private Integer age;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Stud{" +
"address='" + address + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
//方法中必须指定要参与序列化的属性
out.writeInt(age);
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//方法中必须指定要参与反序列化的属性
this.age = in.readInt();
this.name =(String) in.readObject();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Stud stud = new Stud();
stud.setAddress("llll");
stud.setName("lisi");
stud.setAge(11);
FileOutputStream fos = new FileOutputStream("D://test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(stud);
FileInputStream fis = new FileInputStream("D://test.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
stud =(Stud) ois.readObject();
System.out.println(stud);
}
三.serialVersionUID
(1)serialVersionUID是一个标识符,主要用于对象的版本控制。
(2)如果java类已经实现了java.io.Serializable接口,那么默认会在类中添加一个serialVersionUID。但是在该类中添加、删除、修改字段都会生存新的serialVersionUID。这样导致序列化到文件中的serialVersionUID和修改后class中的serialVersionUID不一致,造成对象无法恢复。
(3)java序列化的过程依赖正确的serialVersionUID。无论是否改变类中的字段,serialVersionUID都应该保持不变。所以我们应该在类中显示的添加一个serialVersionUID。
四.Transient,static,final关键字
(1)该修饰符只适用于变量,不适用于方法和类。如果我们不想序列化特定变量以满足安全约束,那么我们将该变量声明为Transient。执行序列化时,JVM会忽略Transient变量的原始值,将默认值保存到文件中。因此Transient意味着不要序列化
(2)静态变量不属于对象状态的一部分,因此它不参与序列化。因此将在静态变量前添加Transient是无用的。
(3)final变量将直接通过值参与序列化。所以在final变量前添加Transient关键字不会产生任何影响。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。