序列化和反序列化

article/2025/9/16 8:42:01

我以前确实对序列化,乃至现在也是不是很熟悉,有时候查找资料,但依旧懵懵懂懂,不过还好遇到一个博主,确定写的挺好的,链接会放再底部

废话不多说,先看官网定义:

序列化 (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;@Overridepublic 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();}
}

运行结果:

image-20211211111125205

Serializable接口的作用

这时候我们来看下Serializable接口,这时候我们点进去,会发现这个接口是个空接口,并没有包含任何方法

image-20211211111237128

我们会感觉这个接口没什么用,那这时我们不继承Serializable接口,运行一下试试

image-20211211111501118

这时候我们会看到这个错误,java.io.NotSerializableException报出了没有序列化异常

然后我们按照错误提示,由源码一直跟到ObjectOutputStreamwriteObject0()方法底层(虽然我看不懂

image-20211211112104380

如果一个对象既不是字符串数组枚举,而且也没有实现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 = -2825960062149872719at 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修饰的字段是不会被序列化的
  • 凡是被transient修饰符修饰的字段也是不会被序列化的 更正:经过@Programming_artist同学的修正和参考这位作者Java中关键字transient解析,这里不能一概而论。只有实现Serializable接口,被transient修饰符修饰的字段也是不会被序列化的 。后面详细解释一下

对于第一个,因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域。
有同学@青铜大神 有个疑问说它测试说经过static修饰后还是能够序列化,这其实是不严谨的。可能是我当时举的例子还不够严谨(历经一年的断更,最近在实习,就忘了更新博客,我的罪过,这里就小更一下吧
我还是以上面的代码为例吧,就不作太大改动了,现在将年龄age加一个static修饰

import java.io.Serializable;
public class Student implements Serializable {private String name;private static Integer age;private Integer score;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", score=" + score +'}';}//getter、setter省略
}

然后我们再写一个测试类

public class test01 {//打印日志private static Logger logger = LoggerFactory.getLogger(test01.class);public static void serialize(Student student,String fileName) throws IOException {ObjectOutputStream objectOutputStream =new ObjectOutputStream( new FileOutputStream( new File(fileName) ) );objectOutputStream.writeObject( student );objectOutputStream.close();logger.info("序列化成功!已经生成{}文件",fileName);logger.info("==============================================");//如果嫌麻烦,也可以用下面语句直接打印,经过一年的沉淀,笔者也是有所成长hhh//System.out.println("序列化成功!已经生成student.txt文件");//System.out.println("==============================================");}public static void deserialize(String fileName) throws IOException, ClassNotFoundException {ObjectInputStream objectInputStream =new ObjectInputStream( new FileInputStream( new File(fileName) ) );Student student = (Student) objectInputStream.readObject();objectInputStream.close();logger.info("反序列化结果:{}",student);//下面日志同样注释了//System.out.println("反序列化结果为:");//System.out.println( student );}public static void main(String[] args) throws IOException, ClassNotFoundException {//生成第一个文件Student studentOne = new Student();studentOne.setName("linko");studentOne.setAge(18);studentOne.setScore(100);String fileNameOne = "studentOne.txt";//模拟序列化的过程serialize(studentOne,fileNameOne);//生成第二个文件Student studentTwo = new Student();studentTwo.setName("实习两年半");//这个词!!!警觉studentTwo.setAge(24);studentTwo.setScore(250);String fileNameTwo = "studentTwo.txt";//模拟序列化的过程serialize(studentTwo,fileNameTwo);//反序列化第一个文件deserialize(fileNameOne);//反序列化第二个文件deserialize(fileNameTwo);}
}

之前第一个写的测试就不更新了,刚好可以看出我这一年小细节上的进步hhh。

image-20230204002442815

这里打印路径可能跟之前不一样了,为什么呢(因为我也忘记把之前写的测试放哪去了)
现在我们重点关注下年龄Age,因为static修饰的就是age
我们可以看到两个反序列化的结果中,年龄age的值是一样的。但是我们从代码中可以看出,我们两次赋值是不一样的。
第一个Student赋值是18,第二个Student赋值是24,但是第一个Student的值在反序列化之后的结果不一样了。
这是为什么呢?
这里需要了解一点jvm中的知识,可以稍微了解一下。
对象的序列化是操作堆内存当中的数据,而静态的变量又称作类变量,其数据存放在方法区(jdk版本不同可能叫法不同)里,类一加载,就初始化了。
而因为静态变量age没有被序列化,根本就没写入文件流中,所以我们打印的值其实一直都是当前Student类的静态变量age的值,而静态变量又是所有的对象共享的一个变量,所以就都是24,也就是直接读取类变量。

对于第二个,就需要了解transient修饰符的作用了(在实现Serializable接口的情况下
如果在序列化某个类的对象时,就是不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码等),那这时就可以用transient修饰符来修饰该字段。

比如在之前定义的Student类中,加入一个密码字段,但是不希望序列化到txt文本,则可以:

image-20211211130118972
jvm是通过Serializable这个标识来实现序列化的,那么我们是否可以自己自定义是否决定序列化呢?答案是可以的,java给我们提供了Externalizable接口,让我们自己实现。
image-20220425105816374
从实现的接口上看,它是继承了Serializable接口,但使用这个接口,需要实现writeExternal以及readExternal这两个方法,来自己实现序列化和反序列化。
实现的过程中,需要自己指定需要序列化的成员变量,此时,static和transient关键词都是不生效的,因为你重写了序列化中的方法。感觉这个static验证也可以再多写点(有时间再更呜
在这里插入图片描述在这里插入图片描述

约束性加持(后面两个有时间再更)

从上面的过程可以看出,序列化和反序列化是有漏洞的,因为序列化和反序列化是有中间过程的,如果别人拿到中间字节流,然后加以伪造或篡改,那反序列化出来的对象就有一定风险了。

单例模式增强

分享链接


http://chatgpt.dhexx.cn/article/NlhZ9kNU.shtml

相关文章

我把序列化玩成了这样,吊锤了一波面试官

我们都知道&#xff0c;新建一个对象的时候实现 Serializeable 接口&#xff0c;但为什么要这么做&#xff1f;什么时候这样子做&#xff1f;这样子做会不会出现幺蛾子&#xff1f;阿粉一个三连差点把自己都问懵逼了…… 那接下来&#xff0c;大家就和阿粉一起简单了解一下这个…

什么是序列化? 如何实现(反)序列化 序列化的应用

1. 什么是序列化与反序列化&#xff0c;什么情况需要序列化1.1 序列化序列化是什么序列化的目的什么情况需要序列化 1.2 反序列化反序列化是什么反序列化的目的 2. Java中的序列化与反序列化2.1 如何实现序列化Java序列化的规定序列化的API实现(反)序列化的示例对象在硬盘上的存…

1.传输线驻波比

Transmission Line & Active Voltage Standing Wave Ratio 1.1 信号完整性概述 数字电路的出现极大地提高了电子产品的抗干扰能力&#xff0c;随着电路的工作频率不断提高&#xff0c;这种抗干扰能力逐渐显得有些“力不从心”。特别是在高速电路的范畴&#xff0c;“理想互…

驻波比,功率计原理,短波机驻波测量

文章内容转载自http://bbs.cqcqcq.com/thread-1627-1-1.html 衡量功率反射大小的量称为「反射系数」&#xff0c;常用Γ (音 gamma) 或ρ (音 rho) 表示。为了讨论简单起见&#xff0c;我们假设负载阻抗为纯阻性的。反射系数定义为&#xff1a; ρ (反射电压波) / (入射电压波)…

入射波反射波和驻波的特性推导

入射波反射波和驻波的基本推导 学习雷达过程中&#xff0c;发现阻抗匹配是一道迈不过去的坎&#xff0c;而阻抗匹配、能量传输与电压驻波比又有千丝万缕的联系&#xff0c;而电压驻波比则与反射波、入射波等相关的特性有关&#xff0c;于是写下此文章记录一下推导过程。 懒得正…

反射系数、驻波比、S参数之间的关系

反射系数、驻波比、S参数之间的关系&#xff01; 转载▼ 回波损耗(Return Loss): 入射功率/反射功率, 为dB数值 反射系数(Г): 反射电压/入射电压, 为标量 电压驻波比(Voltage Standing Wave Ration): 波腹电压/波节电压 S参数: S12为反向传输系数&#xff0c;也就是隔离。…

馈线中的VSWR电压驻波比

在射频信号馈线传输中&#xff0c;信号传输有一个概念&#xff1a;驻波比。 这个概念好理解&#xff0c;就是一个波进去&#xff0c;在终端由于不匹配形成反射波回来。 但是不是那么好想像&#xff0c;叠加后是啥模样的波形。 借助pythonmatplotlib可以方便模拟出来&#xf…

简单了解什么是驻波比?

驻波比全称为电压驻波比&#xff0c;又名VSWR&#xff0c;为英文Voltage Standing Wave Ratio的简写&#xff0c;在理解电压驻波比之前先要明白什么是“驻波”。 假设两个波长相同的波以相反的方向传播&#xff0c;绿线波朝着左方向旋转&#xff0c;蓝线波朝着右方向旋转&…

驻波比理解

VSWR(Voltage Standing Wave Ratio)代表电压驻波比。要完全理解这个术语&#xff0c;需要知道什么是“驻波”。 假设两个波长相同的波以相反的方向传播&#xff0c;如下所示。一个波表示为蓝线&#xff0c;它朝着正确的方向旋转。另一个波用绿线表示&#xff0c;它在左方向旋转…

电压驻波比,回波损耗,传输损耗,电压反射系数,功率传输,功率反射换算表

回波损耗(Return Loss)&#xff1a;入射功率/反射功率, 为dB数值反射系数(Г)&#xff1a;反射电压/入射电压, 为标量电压驻波比(Voltage Standing Wave Ration): 波腹电压/波节电压S参数&#xff1a;S12为反向传输系数&#xff0c;也就是隔离。S21为正向传输系数&#xff0c;也…

天线的驻波比

驻波比是衡量天线性能的重要参数之一,体现了天线向外界空间辐射能量的潜力。这是一个标量的参数,还有史密斯圆图(the Smith Chart)来衡量天线的阻抗特性,可以分析天线是感性还是容性的,并指明了调整天线的方向。 目录​ 一、什么是驻波比VSWR或者SWR

关于驻波比(VSWR)的详细解析

from滤波器 ◆ ◆ ◆ 文 | 滤波器&#xff08;ID:Filter_CN&#xff09; 驻波比&#xff08;VSWR&#xff09;用来检测天馈线系统、射频接头以及所有的连接到基站的射频设备的工作状态。VSWR过高会导致掉话、高误码率&#xff0c;而且由此引入的发射/接受功率的衰减会导致小…

射频回波损耗、反射系数、电压驻波比、S参数的含义与关系

以二端口网络为例&#xff0c;如单根传输线&#xff0c;共有四个S参数&#xff1a;S11&#xff0c;S12&#xff0c;S21&#xff0c;S22&#xff0c;对于互易网络有S12&#xff1d;S21&#xff0c;对于对称网络有S11&#xff1d;S22&#xff0c;对于无耗网络&#xff0c;有S11*S…

【回波损耗(dB)和电压驻波比(VSWR)之间的关系】

回波损耗(dB)和电压驻波比(VSWR)之间的关系 反射系数&#xff08;Г / Rho&#xff09; Г&#xff1d;反射波振幅/入射波振幅 &#xff1d;(传输线特性阻抗-负载阻抗) / (传输线特性阻抗负载阻抗) 回波损耗( RL ) 回波损耗&#xff1a; 回波损耗&#xff0c;又称为反射损…

RF(射频) - VSWR(电压驻波比)

VSWR代表电压驻波比&#xff08;Voltage Standing Wave Ratio&#xff09;。要完全理解这个术语&#xff0c;你需要知道什么是“驻波”。 你可能已经在高中物理课上学到了驻波。只要刷新你的想法&#xff0c;让我解释一下驻波是什么。 假设具有相同波长的两个波沿相反方向传播…

天线参数-自用1

天线参数 1丶 天线谐振频率 Resonance Frequency 2丶驻波比 指的是行驻波的电压波腹值和电压波节值之比 2.1 驻波 驻波即两个反方向波的合成波形&#xff0c;该合成波相位不变&#xff0c;幅度变化&#xff0c;节点位置&#xff08;值 0&#xff09;不会发生变化。 幅度最大…

内联函数(inline)总结

1&#xff1a;定义&#xff1a; 它们看起来象函数&#xff0c;运作起来象函数&#xff0c;比宏(macro)要好得多&#xff0c;使用时还不需要承担函数调用的开销。当内联一个函数时&#xff0c;编译器可以对函数体执行特定环境下的优化工作。这样的优化对"正常"的…

【C++】内联函数理解

内联函数 内联函数的使用是对于C语言中宏函数的一种改进&#xff0c;他继承了宏的优点并避免了宏的缺点。 宏的优点&#xff1a;a. 代码复用性高 b. 宏函数减少栈帧建立&#xff0c;提高效率 宏的缺点&#xff1a;a. 可读性差 b. 没有类型安全检查 c. 不方便调试 C基本不再建议…

在什么情况下方法调用会被内联?

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 方法内联有许多规则。…

【C++】 内联函数详解(搞清内联的本质及用法)

目录 一.什么是内联函数 1.直观上定义&#xff1a; 2.更深入的思考&#xff1a; 二.为什么使用内联函数 1.为什么要代替部分宏定义 2.普通函数频繁调用的过程消耗栈空间 3.更深入的思考 三.内联函数和编译过程的相爱相杀 四.内联函数怎么用&#xff0c;在哪儿用&#…