关于序列化和反序列化

什么是序列化,什么是反序列化

简单来说:

  • 序列化:将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

为什么要进行序列化

我们要将java对象进行网络传输,或者持久化到文件或者文件,数据库或者缓存当中的时候,我们就要进行序列化。将java对象序列化为二进制字节数据。

下面是序列化和反序列化常见应用场景:

  • 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
  • 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
  • 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
  • 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。

序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

如何进行序列化

JDK自带的序列化

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString
public class RpcRequest implements Serializable {private static final long serialVersionUID = 1905122041950251207L;private String requestId;private String interfaceName;private String methodName;private Object[] parameters;private Class<?>[] paramTypes;private RpcMessageTypeEnum rpcMessageTypeEnum;
}

serialVersionUID 有什么作用?

序列化号 serialVersionUID 属于版本控制的作用。反序列化时,会检查 serialVersionUID 是否和当前类的 serialVersionUID 一致。如果 serialVersionUID 不一致则会抛出 InvalidClassException 异常。强烈推荐每个序列化类都手动指定其 serialVersionUID,如果不手动指定,那么编译器会动态生成默认的 serialVersionUID

static 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。但是,serialVersionUID 的序列化做了特殊处理,在序列化时,会将 serialVersionUID 序列化到二进制字节流中;在反序列化时,也会解析它并做一致性判断。

为什么不推荐使用 JDK 自带的序列化?

我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因:

  • 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
  • 性能差:相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
  • 存在安全问题:序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。

比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。这里不做详细说明,可自行查询

如何不让某一个变量不进行序列化

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。

关于 transient 还有几点注意:

  • transient 只能修饰变量,不能修饰类和方法。
  • transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。

探究transient是否能够序列化

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;/*** @author 微信公众号:Java技术栈*/
public class TransientTest {public static void main(String[] args) throws Exception {User user = new User();user.setUsername("Java技术栈");user.setId("javastack");System.out.println("\n序列化之前");System.out.println("username: " + user.getUsername());System.out.println("id: " + user.getId());ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d:/user.txt"));os.writeObject(user);os.flush();os.close();ObjectInputStream is = new ObjectInputStream(new FileInputStream("d:/user.txt"));user = (User) is.readObject();is.close();System.out.println("\n序列化之后");System.out.println("username: " + user.getUsername());System.out.println("id: " + user.getId());}
}/*** @author 微信公众号:Java技术栈*/
class User implements Serializable {private static final long serialVersionUID = 1L;private String username;private transient String id;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getId() {return id;}public void setId(String id) {this.id = id;}}

结果

序列化之前
username: Java技术栈
id: javastack
 
序列化之后
username: Java技术栈
id: null 

示例1在 id 字段上加了 transient 关键字修饰,反序列化出来之后值为 null,说明了被 transient 修饰的变量不能被序列化。

但是 transient只作用于实现 Serializable 接口;

import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;/*** @author 微信公众号:Java技术栈*/
public class ExternalizableTest {public static void main(String[] args) throws Exception {User3 user = new User3();user.setUsername("Java技术栈");user.setId("javastack");ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(new File("javastack")));objectOutput.writeObject(user);ObjectInput objectInput = new ObjectInputStream(new FileInputStream(new File("javastack")));user = (User3) objectInput.readObject();System.out.println(user.getUsername());System.out.println(user.getId());objectOutput.close();objectInput.close();}}/*** @author 微信公众号:Java技术栈*/
class User3 implements Externalizable {private static final long serialVersionUID = 1L;public User3() {}private String username;private transient String id;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getId() {return id;}public void setId(String id) {this.id = id;}@Overridepublic void writeExternal(ObjectOutput objectOutput) throws IOException {objectOutput.writeObject(id);}@Overridepublic void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {id = (String) objectInput.readObject();}}

结果

null
javastack

示例3的 id 被 transient 修改了,为什么还能序列化出来?那是因为 User3 实现了接口 Externalizable,而不是 Serializable。

在 Java 中有两种实现序列化的方式,Serializable 和 Externalizable,可能大部分人只知道 Serializable 而不知道 Externalizable。

这两种序列化方式的区别是:实现了 Serializable 接口是自动序列化的,实现 Externalizable 则需要手动序列化,通过 writeExternal 和 readExternal 方法手动进行,这也是为什么上面的 username 为 null 的原因了。

transient 关键字总结

1)transient修饰的变量不能被序列化;

2)transient只作用于实现 Serializable 接口;

3)transient只能用来修饰普通成员变量字段;

探究static是否能够序列化

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;/*** @author 微信公众号:Java技术栈*/
public class TransientStaticTest {public static void main(String[] args) throws Exception {User2 user = new User2();User2.username = "Java技术栈1";user.setId("javastack");System.out.println("\n序列化之前");System.out.println("username: " + user.getUsername());System.out.println("id: " + user.getId());ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d:/user.txt"));os.writeObject(user);os.flush();os.close();// 在反序列化出来之前,改变静态变量的值User2.username = "Java技术栈2";ObjectInputStream is = new ObjectInputStream(new FileInputStream("d:/user.txt"));user = (User2) is.readObject();is.close();System.out.println("\n序列化之后");System.out.println("username: " + user.getUsername());System.out.println("id: " + user.getId());}
}/*** @author 微信公众号:Java技术栈*/
class User2 implements Serializable {private static final long serialVersionUID = 1L;public static String username;private transient String id;public String getUsername() {return username;}public String getId() {return id;}public void setId(String id) {this.id = id;}}

结果

序列化之前
username: Java技术栈1
id: javastack
 
序列化之后
username: Java技术栈2
id: null

示例2把 username 改为了 public static, 并在反序列化出来之前改变了静态变量的值,结果可以看出序列化之后的值并非序列化进去时的值。

由以上结果分析可知,静态变量不能被序列化,示例2读取出来的是 username 在 JVM 内存中存储的值 ,不管有没有 transient 修饰,静态变量都不能被序列化;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/562433.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Grok-1安装

安装 源代码 huggingface git clone https://github.com/xai-org/grok-1.git # 新建虚拟环境 conda create --prefixD:\CondaEnvs\grok1 python3.11conda activate D:\CondaEnvs\grok1 pip install huggingface_hub[hf_transfer] pip install -U "huggingface_hub[cli]&…

#Linux(Source Insight安装及工程建立)

&#xff08;一&#xff09;发行版&#xff1a;Ubuntu16.04.7 &#xff08;二&#xff09;记录&#xff1a; &#xff08;1&#xff09;安装教程&#xff1a; Source insight 工具安装及使用方法-CSDN博客https://blog.csdn.net/YAOHAIPI/article/details/125191451&#xff…

浅谈前端路由原理hash和history

1、认识前端路由 本质 前端路由的本质&#xff0c;是监听 url 地址或 hash 值的改变&#xff0c;来切换渲染对应的页面组件 前端路由分为两种模式 hash 模式 history 模式 两种模式的对比 2、hash 模式 &#xff08;1&#xff09;hash 定义 hash 模式是一种把前端路由的路…

SpringCloud之网关组件Gateway学习

SpringCloud之网关组件Gateway学习 GateWay简介 Spring Cloud Gateway是Spring Cloud的⼀个全新项目&#xff0c;目标是取代Netflix Zuul&#xff0c;它基于Spring5.0SpringBoot2.0WebFlux&#xff08;基于高性能的Reactor模式响应式通信框架Netty&#xff0c;异步⾮阻塞模型…

Wireshark TS | DNS 案例分析之外的思考

前言 承接之前一篇《Packet Challenge 之 DNS 案例分析》&#xff0c;在数据包跟踪文件 dnsing.pcapng 中&#xff0c;关于第 4 题&#xff08;What is the largest DNS response time seen in this trace file? &#xff09;的分析过程中曾经碰到一个小问题&#xff0c;主要…

Pycharm小妙招之Anaconda离线配环境

Pycharm小妙招之Anaconda离线配环境———如何给无法联网的电脑配python环境&#xff1f; 1. 预备工作2. 电脑1导出包2.1 环境路径2.2 压缩py38导出至U盘 3. 电脑2导入包4. 验证是否导入成功4.1 conda查看是否导入4.2 pycharm查看能否使用 1. 预备工作 WINDOWS系统电脑1(在线)…

设计模式及其在项目、框架中的应用

设计模式的作用&#xff1a; 1、类之间关系图&#xff0c;明确的角色及其关系、作用&#xff1b; 2、符合开闭原则&#xff0c;职责明确&#xff0c;并且开放的拓展点可以有效应对后期的变化。 &#xff08;一&#xff09;、责任链模式 适用场景&#xff1a; 在一个流程中&…

mysql无法看到3306端口监听

参考:https://blog.csdn.net/shumeigang/article/details/103902459 mysql> show global variables like ‘port’; 是0 原因是我的my.cnf有话&#xff1a; skip-network 或 注释掉&#xff0c;然后重新启动下数据库&#xff0c;运行netstat -an|grep 3306 就可以看到了

如何进行设备的非对称性能测试

非对称性能测试介绍 RFC2544是RFC组织提出的用于评测网络互联设备&#xff08;防火墙、IDS、Switch等&#xff09;的国际标准。主要是对RFC1242中定义的性能评测参数的具体测试方法、结果的提交形式作了较详细的规定。标准中定义了4个重要的参数&#xff1a;吞吐量&#xff08…

思通舆情 是一款开源免费的舆情系统 介绍

思通舆情 是一款开源免费的舆情系统。 支持本地化部署&#xff0c;支持在线体验。 支持对海量舆情数据分析和挖掘。 无论你是使用者还是共同完善的开发者&#xff0c;欢迎 pull request 或者 留言对我们提出建议。 您的支持和参与就是我们坚持开源的动力&#xff01;请 sta…

【Linux】程序地址空间

程序地址空间 初识程序地址空间进程地址空间一些补充虚拟地址转物理地址的工作谁来做&#xff1f;进程地址空间存在的意义回看new/malloc 进程中的写时拷贝一点延申 初识程序地址空间 运行结果如下&#xff1a;   我们发现父进程和子进程打印出来的值和地址都是一样的&#…

花生壳 | ubuntu安装和卸载花生壳

一、到花生壳官网下载linux版本的花生壳 一般下载到Downlaods文件夹 进入文件夹 dpkg -i phddns_5_1_amd64.deb #安装花生壳 dpkg -r phddns #卸载花生壳 登录花生壳管理网站 http://b.oray.com 在Ubuntu中输入命令查看sn码&#xff0c;默认密码为admin phddns status …