序列化和反序列化
序列化
什么是序列化
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
所以说,序列化是一种技术,Java只是以某种形式实现了序列化
序列化的目的
序列化最终的目的是为了对象可以跨平台存储,和进行网络传输
什么情况需要序列化
经过上面的分析,已经很明确的知道凡是需要进行“跨平台存储”和”网络传输”的数据,都需要进行序列化。
本质上存储和网络传输 都需要经过 把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息。
反序列化
什么是反序列化
经过上面的知识点,我们已经知道,序列化是将对象的状态信息转换为可以存储或传输的形式的过程;把对象转换为字节序列的过程称为对象的序列化。
那么把字节序列恢复为对象的过程称为对象的反序列化。
小结
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送;
发送方需要把这个类型数据(对象)转换为二进制字节序列,才能在网络上传送;(序列化)
接收方则需要把字节序列再恢复为对象。(反序列化)
Java中的序列化
在Java中,一切皆对象,当我们需要实现对象的序列化时,也就需要将Java对象转换成一种对应的字节形式存储;
在Java的OutputStream类下面的子类ObjectOutputStream类就有对应的WriteObject(Object object) 方法来实现序列化,其中的参数object就要求实现了java的序列化的接口。
Serializable
java序列化的规定
Java只能将支持 java.io.Serializable 接口的对象写入流中;每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包
如果一个对象没有实现Serializable序列化接口,而就去使用了ObejctOutputStream去序列化对象,运行时则会报错
注意:如果类的属性包含其他的类,那么那些类也需要实现序列化接口,否则也会报错。
什么是Serializable接口
Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口。
Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。
我们可以看看java源码:
一个接口里面什么内容都没有,我们可以将它理解成一个标识接口。
怎么理解这句话呢?
比如在课堂上有位学生遇到一个问题,于是举手向老师请教,这时老师帮他解答,那么这位学生的举手其实就是一个标识,自己解决不了问题请教老师帮忙解决。在Java中的这个Serializable接口其实是给jvm看的,通知jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。
Serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。
serialversionUID变量
关于serialversionUID,可以看看jdk1.8手册中关于serialversionUID的说明
Serializable (Java Platform SE 8 )
- 序列化运行时将每个可序列化的类与serialVersionUID相关联,该序列号在反序列化期间用于验证序列化对象的发送者和接收者是否已加载与该序列化兼容的对象的类。
- 如果接收方加载了一个具有不同于相应发件人类的serialVersionUID的对象的类,则反序列化将导致InvalidClassException 。 一个可序列化的类可以通过声明一个名为"serialVersionUID"的字段来显式地声明它自己的serialVersionUID,该字段必须是static,final,long
- 序列化类没有显式声明serialVersionUID,则序列化运行时将根据Java(TM)对象序列化规范中所述的类的各个方面计算该类的默认serialVersionUID值。
- 但是, 强烈建议所有可序列化的类都明确声明serialVersionUID值,因为默认的serialVersionUID对class信息非常敏感,因此可能会在反序列化时抛出InvalidClassException异常
再来看看java源码中的serialversion实现类吧
拿大家最为熟知的String类来举例:
在string类中,不仅继承了java.io.Serializable类,还定义了serialVersionUID,
注意:serialVersionUID的定义:
private static final long serialVersionUID = -6849794470754667710L;
序列化的使用
下面我们可以通过例子来实现将序列化的对象存储到文件,然后再将其从文件中反序列化为对象,代码示例如下:
定义一个序列化对象User:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId; private String userName;
public User(String userId, String userName) {
this.userId = userId;
this.userName = userName;
}
}
然后我们编写测试类,来对该对象进行读写操作,我们先测试将该对象写入一个文件:
public class SerializableTest {
/** * 将User对象作为文本写入磁盘 */
public static void writeObj() {
User user = new User("1001", "Joe");
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/guanliyuan/user.txt"));
objectOutputStream.writeObject(user);
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
writeObj();
}
}
运行上述代码,我们就将User对象及其携带的数据写入了文本user.txt中。
接下来,我们继续编写测试代码,尝试将之前持久化写入user.txt文件的对象数据再次转化为Java对象,代码如下:
public class SerializableTest {
/** * 将类从文本中提取并赋值给内存中的类 */
public static void readObj() {
try {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/guanliyuan/user.txt"));
try {
Object object = objectInputStream.readObject();
User user = (User) object;
System.out.println(user);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
readObj();
}
}
通过反序列化操作,可以再次将持久化的对象字节流数据通过IO转化为Java对象,结果如下:
cn.wudimanong.serializable.User@6f496d9f
序列化与反序列化操作过程就是这么的简单。只需要将User写入到文件中,然后再从文件中进行恢复,恢复后得到的内容与之前完全一样,但是两者是不同的对象。