当前位置: 首页 > news >正文

【Java中序列化的原理是什么(解析)】

在这里插入图片描述

🍁序列化的原理是什么?

  • 🍁典型-----解析
  • 🍁拓展知识仓
    • 🍁Serializable 和 Externalizable 接门有何不同?
  • 🍁如果序列化后的文件或者原始类被篡改,还能被反序列化吗?
    • 🍁serialVersionUID 有何用途? 如果没定义会有什么问题?
  • 🍁在Java中,有哪些好的序列化框架,有什么好处?


🍁典型-----解析


序列化是将对象转换为可传输格式的过程。是一种数据的持久化手段。一般广泛应用于网络传输,RMI和RPC等场景中。 几乎所有的商用编程语言都有序列化的能力,不管是数据存储到硬盘,还是通过网络的微服务传输,都需要序列化能力。


在Java的序列化机制中,如果是String枚举或者实现了Serializable接口的类,均可以通过Java的序列化机制将类序列化为符合编码的数据流,然后通过InputStream和OutputStream将内存中的类持久化到硬盘或者网络中;同时,也可以通过反序列化机制将磁盘中的字节码再转换成内存中的类。


如果一个类想被序列化,需要实现Serializable接口。否则将抛出NotSerializableException异常。Serializable接门没有方法或字段,仅用于标识可序列化的语义。


自定义类通过实现Serializable接口做标识,进而在10中实现序列化和反序列化,具体的执行路径如下:


#write0bject -> #writeobjecto(判断类是否是自定义类) -> writeOrdinary0bject(区分Serializable和Externalizable) -> writeSerialData(序列化fields) -> invokewriteobject(反射调用类自己的序列化策略)


其中,在invokeWriteObject的阶段,系统就会处理自定义类的序列化方案。


这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。


看一段代码,对象的序列化和反序列化:


import java.io.*;  
import java.util.*;  class Employee implements Serializable {  private String name;  private int age;  private Department department;  public Employee(String name, int age, Department department) {  this.name = name;  this.age = age;  this.department = department;  }  public String toString() {  return "Employee [name=" + name + ", age=" + age + ", department=" + department + "]";  }  
}  class Department implements Serializable {  private String name;  private List<Employee> employees;  public Department(String name) {  this.name = name;  this.employees = new ArrayList<>();  }  public void addEmployee(Employee employee) {  employees.add(employee);  }  public String toString() {  return "Department [name=" + name + ", employees=" + employees + "]";  }  
}  public class ComplexSerializationDemo {  public static void main(String[] args) throws IOException, ClassNotFoundException {  // 创建对象关系图  Department department1 = new Department("HR");  Department department2 = new Department("IT");  Employee employee1 = new Employee("Alice", 25, department1);  Employee employee2 = new Employee("Bob", 30, department2);  Employee employee3 = new Employee("Charlie", 35, department1);  department1.addEmployee(employee1);  department1.addEmployee(employee3);  department2.addEmployee(employee2);  // 序列化对象关系图到文件  FileOutputStream fileOut = new FileOutputStream("complexObject.ser");  ObjectOutputStream out = new ObjectOutputStream(fileOut);  out.writeObject(department1);  out.writeObject(department2);  out.writeObject(employee1);  out.writeObject(employee2);  out.writeObject(employee3);  out.close();  fileOut.close();  System.out.println("对象关系图已序列化到文件complexObject.ser");  // 从文件中反序列化对象关系图  FileInputStream fileIn = new FileInputStream("complexObject.ser");  ObjectInputStream in = new ObjectInputStream(fileIn);  Department department1_ = (Department) in.readObject();  Department department2_ = (Department) in.readObject();  Employee employee1_ = (Employee) in.readObject();  Employee employee2_ = (Employee) in.readObject();  Employee employee3_ = (Employee) in.readObject();  in.close();  fileIn.close();  System.out.println("从文件complexObject.ser反序列化的对象关系图:");  System.out.println("Department 1: " + department1_);  System.out.println("Department 2: " + department2_);  System.out.println("Employee 1: " + employee1_);  System.out.println("Employee 2: " + employee2_);  System.out.println("Employee 3: " + employee3_);  }  
}

🍁拓展知识仓


🍁Serializable 和 Externalizable 接门有何不同?


类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。


当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出NotSerializableException。


如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该实现Java.io.Serializable接口。


Externalizable继承了Serializable,该接口中定义了两个抽象方法: writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法,如果没有在这两个方法中定义序列化实现细节,那么序列化之后,对象内容为空。实现Externalizable接口的类必须要提供一个public的无参的构造器。


所以,实现Externalizable,并实现writeExternal0和readExternal()方法可以指定序列化哪些属性。


🍁如果序列化后的文件或者原始类被篡改,还能被反序列化吗?


🍁serialVersionUID 有何用途? 如果没定义会有什么问题?


序列化是将对象的状态信息转换为可存储或传输的形式的过程。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。


而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。


把Java对象序列化成可存诸或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这人对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。


但是,虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID 是否 致,即serialVersionUID要求 一致。


在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。这样做是为了保证安全,因为文件存储中的内容可能被篡改。


当实现iava.io.Serializable接口的类没有显式地定义一个serialVersionUID变量时候,Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件没有发生变化,就算重偏译多次,serialVersionUID也不会变化的。但是,如果发生了变化,那么这个文件对应的serialVersionUID也就会发生变化。


基于以上原理,如果我们一个类实现了Serializable接口,但是没有定义serialVersionUID,然后序列化,在序列化之后,由于某些原因,我们对该类做了变更,重新启动应用后,我们相对之前序列化过的对象进行反序列化的话就会报错。


看一段代码,如何使用自定义的序列化方法,以及如何处理序列化过程中的异常:


import java.io.*;  
import java.util.*;  class Employee implements Serializable {  private static final long serialVersionUID = 1L;  private String name;  private int age;  private Set<String> skills;  public Employee(String name, int age, Set<String> skills) {  this.name = name;  this.age = age;  this.skills = skills;  }  public void displayInfo() {  System.out.println("Name: " + name);  System.out.println("Age: " + age);  System.out.println("Skills: " + skills);  }  // 自定义的序列化方法  private void writeObject(ObjectOutputStream out) throws IOException {  out.writeUTF(name);  out.writeInt(age);  out.writeObject(skills);  }  // 自定义的反序列化方法  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  name = in.readUTF();  age = in.readInt();  skills = (Set<String>) in.readObject();  }  
}  public class SerializationDemo {  public static void main(String[] args) {  try {  // 创建一个 Employee 对象并序列化  Set<String> skills = new HashSet<>();  skills.add("Java");  skills.add("Python");  Employee employee = new Employee("John", 25, skills);  ByteArrayOutputStream baos = new ByteArrayOutputStream();  ObjectOutputStream oos = new ObjectOutputStream(baos);  employee.writeObject(oos);  // 使用自定义的序列化方法  oos.close();  // 反序列化 Employee 对象(确保使用相同的 serialVersionUID)  ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());  ObjectInputStream ois = new ObjectInputStream(bais);  Employee deserializedEmployee = new Employee("", 0, new HashSet<>());  // 创建一个新的 Employee 对象用于反序列化  deserializedEmployee.readObject(ois);  // 使用自定义的反序列化方法  ois.close();  deserializedEmployee.displayInfo();  // 输出员工的详细信息  } catch (IOException e) {  e.printStackTrace();  } catch (ClassNotFoundException e) {  e.printStackTrace();  }  }  
}

在上面的示例中,为 Employee 类实现了 writeObject 和 readObject 方法,以自定义序列化和反序列化的过程。我们在 writeObject 方法中使用了 ObjectOutputStream 的 writeUTF、writeInt 和 writeObject 方法来写入员工的姓名、年龄和技能集合。在 readObject 方法中,我们使用 ObjectInputStream 的 readUTF、readInt 和 readObject 方法来读取这些值。我们还创建了一个新的 Employee 对象用于反序列化,并调用了自定义的反序列化方法。这个示例展示了如何处理序列化和反序列化过程中的异常,并展示了如何使用自定义的序列化方法来控制对象的序列化和反序列化过程。


🍁在Java中,有哪些好的序列化框架,有什么好处?


Java中常用的序列化框架:


java、 kryo、hessian、 protostuff、 gson、fastjson等。


Kryo: 速度快,序列化后体积小: 跨语言支持较复杂


Hessian: 默认支持跨语言: 效率不高


Protostuff: 速度快,基于protobuf; 需静态编译


Protostuff-Runtime: 无需静态编译,但序列化前需预先传入schema; 不支持无默认构造函数的类,反序列化时需用户自己初始化序列化后的对象,其只负责将该对象进行赋值


Java: 使用方便,可序列化所有类;速度慢,占空间

相关文章:

  • [每周一更]-(第40期):GIT更换远程仓库地址
  • 八股文打卡day15——计算机网络(15)
  • Spring AOP—深入动态代理 万字详解(通俗易懂)
  • 组合[中等]
  • 医院绩效考核系统源码,java源码,商业级医院绩效核算系统源码
  • docker-compose部署kafka
  • [Angular] 笔记 8:list/detail 页面以及@Input
  • 嵌入式开发网络配置——windows连热点,开发板和电脑网线直连
  • 从a类到b类理解原型链
  • Python开发GUI常用库PyQt6和PySide6介绍之三:交互和通信方式讲解
  • 第八章 创建Callout Library - ZFentry 链接选项
  • Spring DefaultListableBeanFactory源码分析
  • mvtec3d
  • [架构之路-265]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 如何做好详细设计
  • D9741 PWM控制器电路,定时闩锁、短路保护电路,输出基准电压(2.5V) 采用SOP16封装
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • ES6系列(二)变量的解构赋值
  • java第三方包学习之lombok
  • Logstash 参考指南(目录)
  • maya建模与骨骼动画快速实现人工鱼
  • ReactNativeweexDeviceOne对比
  • vue脚手架vue-cli
  • 读懂package.json -- 依赖管理
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 区块链分支循环
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 说说动画卡顿的解决方案
  • 温故知新之javascript面向对象
  • 线上 python http server profile 实践
  • 一道面试题引发的“血案”
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • ​2021半年盘点,不想你错过的重磅新书
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • (20050108)又读《平凡的世界》
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (汇总)os模块以及shutil模块对文件的操作
  • (五)关系数据库标准语言SQL
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • * 论文笔记 【Wide Deep Learning for Recommender Systems】
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .net 获取url的方法
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • .NET下的多线程编程—1-线程机制概述
  • .Net中wcf服务生成及调用
  • /etc/skel 目录作用
  • :O)修改linux硬件时间
  • @Transient注解