Java中的Serializable接口之“序列化”与“反序列化”

你是不是想问:为什么有的类实现Serializable接口?还有什么是序列化和反序列化?
带着这个问题往下看,看完就明白了!

序列化与反序列化
  • 序列化是指把对象转换为字节序列的过程,我们称之为对象的序列化,就是把内存中的这些对象变成一连串的字节(bytes)描述的过程,也就是把一个对象,保存到一个持久的文件中,而ObjectOutputStream 类就是用来做这个事的。
  • 反序列化则相反,就是把持久化的字节文件数据恢复为对象的过程,ObjectInputStream类就是做这个事情的 。那么什么情况下需要序列化呢?大概有这样两类比较常见的场景:1)、需要把内存中的对象状态数据保存到一个文件或者数据库中的时候,这个场景是比较常见的,例如我们利用mybatis框架编写持久层insert对象数据到数据库中时;2)、网络通信时需要用套接字在网络中传送对象时,如我们使用RPC协议进行网络通信时;
Serializable接口概述

Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口。Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重新创建对象。

而这一点对于面向对象的编程语言来说是非常重要的,因为无论什么编程语言,其底层涉及IO操作的部分还是由操作系统其帮其完成的,而底层IO操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转化为编程语言类型的特定数据类型。而Java作为一门面向对象的编程语言,对象作为其主要数据的类型载体,为了完成对象数据的读写操作,也就需要一种方式来让JVM知道在进行IO操作时如何将对象数据转换为字节流,以及如何将字节流数据转换为特定的对象,而Serializable接口就承担了这样一个角色。

看下面代码实例:

package com.hqq.serializable;

import java.io.Serializable;

public class Student implements Serializable {

	private static final long serialVersionUID = 1L;

	private String stuID;
	private String name;

	public Student(String stuID, String name) {
		this.stuID = stuID;
		this.name = name;
	}

	@Override
	public String toString() {
		return "[" + stuID + "," + name + "]";
	}
}

package com.hqq.serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main {
	public static void main(String[] args) {
		String filePath = "C:\\Users\\GGboy\\Desktop\\file.txt";
		
		//序列化
//		Main.writeObject(filePath);
		
		//反序列化
		Main.readObject(filePath);
	}

	/**
	 * 将对象持久化存储在文件中,也就是序列化对象
	 */
	public static void writeObject(String filePath) {
		Student stu = new Student("1611640303", "二狗子");
		try {
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(
					new FileOutputStream(new File(filePath)));
			objectOutputStream.writeObject(stu);
			
			/*
			 * 注意:这种关闭流的操作是不对的,close一定要在finally里面关闭, 
			 * 那时候只要把objectInputStream的声明放到try语句外面就OK
			 * 下面的close使用才是正确的
			 */
			objectOutputStream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 根据存储在文件中的对象的信息讲对象读取出来,也就是对象的反序列化
	 */
	public static void readObject(String filePath) {
		
		ObjectInputStream objectInputStream = null;
		
		try {
			objectInputStream = new ObjectInputStream(
					new FileInputStream(new File(filePath)));
			try {
				Object object = objectInputStream.readObject();
				Student stu = (Student) object;
				System.out.println(stu.toString());
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} finally {
				if (objectInputStream != null) {
					objectInputStream.close();
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

序列化结果:
在这里插入图片描述

  • 这时候有的人输出的文件中文可能是乱码,出现结果是因为你的IDE编译器的编码方式和你的电脑查看文件时的工具的编码方式不一样,我一般情况下会把所有的编码方式设置成utf-8,对编码方式不了解的可以看这个https://blog.csdn.net/qq_42570601/article/details/103381092,如果对IDE修改编码方式的操作不会的话可以百度,夜很深了,想早点睡觉,主要有两种,对包和类修改,对整个编译器修改,修改完之后把原来出现乱码的代码删除,在原样重新输入一遍就OK。

反序列化结果:
在这里插入图片描述

如果我们序列化与反序列化的时候不实现Serializable接口会怎样?看下面结果:
在这里插入图片描述

而且在反序列化的时候,如果private static final long serialVersionUID = 1L;这段代码中的serialVersionUID 的值和序列化时候的serialVersionUID 的值不一样,也会报错:
在这里插入图片描述

关于serialVersionUID

对于JVM来说,要进行持久化的类必须要有一个标记,只有持有这个标记JVM才允许类创建的对象可以通过其IO系统转换为字节数据,从而实现持久化,而这个标记就是Serializable接口。而在反序列化的过程中则需要使用serialVersionUID来确定由那个类来加载这个对象,所以我们在实现Serializable接口的时候,一般还会要去尽量显示地定义serialVersionUID,如:

private static final long serialVersionUID = 1L;

还有一个上面已经说过了,在反序列化的过程中,如果接收方为对象加载了一个类,如果该对象的serialVersionUID与对应持久化时的类不同,那么反序列化的过程中将会导致InvalidClassException异常。
在这里插入图片描述

如果我们在序列化中没有显示地声明serialVersionUID,则序列化运行时将会根据该类的各个方面计算该类默认的serialVersionUID值。但是,Java官方强烈建议所有要序列化的类都显示地声明serialVersionUID字段,因为如果高度依赖于JVM默认生成serialVersionUID,可能会导致其与编译器的实现细节耦合,这样可能会导致在反序列化的过程中发生意外的InvalidClassException异常。因此,为了保证跨不同Java编译器实现的serialVersionUID值的一致,实现Serializable接口的必须显示地声明serialVersionUID字段。

此外serialVersionUID字段地声明要尽可能使用private关键字修饰,这是因为该字段的声明只适用于声明的类,该字段作为成员变量被子类继承是没有用处的!有个特殊的地方需要注意的是,数组类是不能显示地声明serialVersionUID的,因为它们始终具有默认计算的值,不过数组类反序列化过程中也是放弃了匹配serialVersionUID值的要求。

参考文献:
https://www.w3cschool.cn/java/java-serialization.html
https://developer.51cto.com/art/201905/596334.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值