我以前确实对序列化,乃至现在也是不是很熟悉,有时候查找资料,但依旧懵懵懂懂,不过还好遇到一个博主,确定写的挺好的,链接会放再底部
废话不多说,先看官网定义:
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
序列化主要有两个用途
- 把对象的字节序列永久保存到硬盘上,通常存放在一个文件中(序列化对象)
- 在网络上传送对象的字节序列(网络传输对象)
实际上就是将数据持久化,防止一直存储在内存当中,消耗内存资源。而且序列化后也能更好的便于网络运输何传播
- 序列化:将java对象转换为字节序列
- 反序列化:把字节序列回复为原先的java对象
对象如何序列化?
java目前没有一个关键字是直接定义一个所谓的“可持久化”对象
对象的持久化和反持久化需要靠程序员在代码里手动显式地进行序列化和反序列化还原的动作。
举个例子,假如我们要对Student
类对象序列化到一个名为student.txt
的文本文件中,然后再通过文本文件反序列化成Student
类对象:
按我的理解就是序列化:将一个对象转化为一种格式,能够更好的传输和电脑理解。
反序列化是转换过来,便于人们观看的。
先写一个实体类
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private Integer age;
private Integer score;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
//getter、setter省略
}
然后填写一个测试类
public class test01 {
public static void serialize( ) throws IOException {
Student student = new Student();
student.setName("linko");
student.setAge( 18 );
student.setScore( 1000 );
ObjectOutputStream objectOutputStream =
new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
objectOutputStream.writeObject( student );
objectOutputStream.close();
System.out.println("序列化成功!已经生成student.txt文件");
System.out.println("==============================================");
}
public static void deserialize( ) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream =
new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
Student student = (Student) objectInputStream.readObject();
objectInputStream.close();
System.out.println("反序列化结果为:");
System.out.println( student );
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
serialize();
deserialize();
}
}
运行结果:
Serializable
接口的作用
这时候我们来看下Serializable接口,这时候我们点进去,会发现这个接口是个空接口,并没有包含任何方法
我们会感觉这个接口没什么用,那这时我们不继承Serializable接口,运行一下试试
这时候我们会看到这个错误,java.io.NotSerializableException
报出了没有序列化异常
然后我们按照错误提示,由源码一直跟到ObjectOutputStream
的writeObject0()
方法底层(虽然我看不懂
如果一个对象既不是字符串、数组、枚举,而且也没有实现Serializable
接口的话,在序列化时就会抛出NotSerializableException
异常!
说的太好了哭,强烈推荐底部链接的那个博主
他一说我就大概知道懂什么意思了。
先说说instanceof
,它是用来测试一个对象是否为一个类的实例
// remaining cases
//判断这个obj对象是否是String类
if (obj instanceof String) {
writeString((String) obj, unshared);
//判断这个obj对象是否是数组
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
//判断这个obj对象是否是枚举
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
//判断这个obj对象是否是实现序列化
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
//否则就报错
} else {
//报错是否由有扩展信息,详细信息可以观看源码
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
所以Serializable
接口只是一个做标记用的
它告诉代码只要是实现了Serializable
接口的类都是可以被序列化的!然而真正的序列化动作不需要靠它完成。
serialVersionUID
号有何用?
在我们写项目的时候,我们会经常看到这么这么一个语句。
定义一个名为serialVersionUID
的字段
private static final long serialVersionUID = -4392658638228508589L;
为什么有这句话呢,为什么要搞一个名为**serialVersionUID
**的序列号
继续做一个简单实验,依旧是上面的Student
类,然后我们先不写序列号。然后在Student
中添加一个字段,并添加序列号
private static final long serialVersionUID = -4392658638228508589L;
private String name;
private Integer age;
private Integer score;
private Long studentId;
然后再次序列化和反序列化一下
再然后我们拿刚才已经序列化好的student.txt
文件,然后试着反序列化一下。
在test01
测试类中,我们将主函数的序列化调用的函数给删掉,再把序列号给删掉
public static void main(String[] args) throws IOException, ClassNotFoundException {
//serialize();
deserialize();
}
我们会发现报错了。太长了,没能截屏出来
它说序列号不一致
Exception in thread "main" java.io.InvalidClassException: demo01.Student; local class incompatible: stream classdesc serialVersionUID = -4392658638228508589, local class serialVersionUID = -2825960062149872719
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at demo01.test01.deserialize(test01.java:32)
at demo01.test01.main(test01.java:41)
从这几个地方我们可以看出几个重要的信息
- serialVersionUID是序列化前后的唯一标识符
- 默认如果没有人为显式定义过
serialVersionUID
,那编译器会为它自动声明一个!
第1个问题: serialVersionUID
序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程。
所以,为了serialVersionUID
的确定性,写代码时还是建议,凡是implements Serializable
的类,都最好人为显式地为它声明一个serialVersionUID
明确值!
当然,如果不想手动赋值,你也可以借助IDE的自动添加功能,比如我使用的IntelliJ IDEA
,按alt + enter
就可以为类自动生成和添加serialVersionUID
字段,十分方便;
两种特殊情况
- 凡是被
static
修饰的字段是不会被序列化的 凡是被更正:经过@Programming_artist同学的修正和参考这位作者Java中关键字transient解析,这里不能一概而论。只有实现transient
修饰符修饰的字段也是不会被序列化的Serializable
接口,被transient
修饰符修饰的字段也是不会被序列化的 。后面详细解释一下
对于第一个,因为序列化保存的是对象的状态而非类的状态,所以会忽略static
静态域。
对于第二个,就需要了解transient
修饰符的作用了(在实现Serializable
接口的情况下
如果在序列化某个类的对象时,就是不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码
等),那这时就可以用transient
修饰符来修饰该字段。
比如在之前定义的Student
类中,加入一个密码字段,但是不希望序列化到txt
文本,则可以:
jvm是通过Serializable
这个标识来实现序列化的,那么我们是否可以自己自定义是否决定序列化呢?答案是可以的,java给我们提供了Externalizable
接口,让我们自己实现。
从实现的接口上看,它是继承了Serializable
接口,但使用这个接口,需要实现writeExternal
以及readExternal
这两个方法,来自己实现序列化和反序列化。
实现的过程中,需要自己指定需要序列化的成员变量,此时,static和transient关键词都是不生效的,因为你重写了序列化中的方法。感觉这个static验证也可以再多写点(有时间再更呜
约束性加持(后面两个有时间再更)
从上面的过程可以看出,序列化和反序列化是有漏洞的,因为序列化和反序列化是有中间过程的,如果别人拿到中间字节流,然后加以伪造或篡改,那反序列化出来的对象就有一定风险了。
单例模式增强
分享链接