Java 入门指南:Java IO流 —— 序列化与反序列化

news2024/9/19 10:04:40

序列化

序列化是指将对象转换为字节流的过程,以便能够将其存储到文件、内存、网络传输等介质中,或者在不同的进程、网络或机器之间进行数据交换。

序列化的逆过程称为反序列化,即将字节流转换为对象。过反序列化,可以从存储介质或网络传输中读取数据,并重新构建对象。

Java中的序列化通过实现 Serializable 接口来实现。Serializable 接口是一个标记接口,没有方法需要实现。当一个类实现了 Serializable 接口时,表示该类的实例对象可以被序列化,如果一个字段不需要序列化,则需要使用 transient 进行修饰。

ObjectOutputStream

java.io.ObjectOutputStream 继承自 OutputStream 类,它可以将Java对象序列化成字节流,以便在文件、网络传输等场景中进行存储、传输或持久化操作。

构造方法:ObjectOutputStream(OutputStream out)
该构造方法接收一个 OutputStream 对象作为参数,用于将序列化后的字节序列输出到指定的输出流中。

ObjectOutputStream 序列化的时候会依次调用 writeObject() → writeObject0() → writeOrdinaryObject() → writeSerialData() → invokeWriteObject() → defaultWriteFields()

wirteObject()

writeObject (Object obj) 方法,该方法是 ObjectOutputStream 类中用于将对象序列化成字节序列并输出到输出流中的方法,可以处理对象之间的引用关系、继承关系、静态字段和 transient 字段。

ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException

部分源码

![[Pasted image 20231102190402.png]]

ObjectInputStream

ObjectInputStream 可以读取 ObjectOutputStream 写入的字节流,并将其反序列化为相应的原始的对象(包含 对象的数据对象的类型对象中存储的属性 等信息)。序列化之前是什么样子,反序列化后就是什么样子。

ObjectInputStream 在反序列化的时候会依次调用 readObject() → readObject0() → readOrdinaryObject() → readSerialData() → defaultReadFields()

构造方法

ObjectInputStream(InputStream in):创建一个指定 InputStreamObjectInputStream

其中,ObjectInputStreamreadObject 方法用来读取指定文件中的对象

readObject()

ObjectInputStream 类中的 readObject 方法用于从输入流中读取并反序列化一个对象。这是 Java 序列化机制中的一个重要组成部分,使得对象的状态可以在不同进程之间传输或持久化到文件中。

读取并反序列化对象

下面是一个使用 ObjectInputStreamreadObject 方法来读取并反序列化对象的示例。

import java.io.*;

public class ObjectInputStreamExample {

    public static void main(String[] args) {
        // 定义对象存储文件路径
        String filePath = "person.dat";

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
            // 读取并反序列化对象
            Person person = (Person) ois.readObject();

            // 输出反序列化后的对象
            System.out.println(person);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

// 用于序列化的类
class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

代码解释:

  1. 创建序列化对象

首先,需要有一个实现了 Serializable 接口的类。在这个例子中,Person 类实现了 Serializable 接口,并且有两个属性:nameage

  1. 反序列化对象
  • 创建 ObjectInputStream:使用 FileInputStream 创建一个 ObjectInputStream 对象,指向包含序列化对象的文件。

  • 读取对象:通过调用 readObject 方法从输入流中读取并反序列化对象。注意,readObject 方法返回的是 Object 类型的对象,因此需要强制转换为具体的类型(在这里是 Person 类型)。

  • 输出对象:将反序列化后的对象输出到控制台。

  1. 处理异常
  • IOException:当读取过程中发生 I/O 错误时抛出。
  • ClassNotFoundException:当反序列化的对象类无法找到时抛出。这种情况通常发生在序列化对象所在的类没有在目标环境中存在的情况。

序列化优点

  • 数据持久化:序列化可以将对象保存到文件系统、数据库或其他存储介质中,以便在需要时进行读取和恢复。

  • 网络传输:序列化可以将对象转换为字节流,方便在网络上进行传输和交换。

  • 分布式系统:序列化使得在分布式系统中可以通过网络传递对象,共享数据。

序列化注意事项

  • 序列化版本控制:为了避免对象的不同版本之间的兼容性问题,推荐在进行对象序列化时定义 serialVersionUID,并在后续的版本中进行维护。

  • 敏感数据处理:在进行序列化时,敏感数据(如密码、私密信息)需要加密或者通过 transient 关键字进行忽略。

  • 不可序列化的对象:某些对象不能被序列化,如线程、套接字等,需要进行特殊处理或者将其字段标记为 transient

总结来说,序列化是将对象转换为字节流的过程,使得对象可以持久化存储、网络传输和分布式系统中共享。Java提供了简单易用的序列化机制,能够方便地实现对象的序列化和反序列化操作。但要注意版本控制、敏感数据保护和不可序列化对象的处理。

反序列化

反序列化(Deserialization) 是将序列化后的字节流转换回对象的过程。在 Java 中,可以使用 ObjectInputStream 类来实现反序列化操作。

在进行反序列化之前,要确保序列化和反序列化的类是相同的版本,即类的结构没有发生变化。否则,在反序列化时可能会出现 InvalidClassException 或其他兼容性问题。

反序列化的过程是将存储在字节流中的对象信息读取出来,并还原为对象实例。以下是进行反序列化的基本步骤:

  1. 创建一个 ObjectInputStream 对象,并传入一个 InputStream(如 FileInputStream)参数,用于读取字节流数据。

  2. 使用 ObjectInputStream 对象的 readObject() 方法从字节流中读取对象,并将其转换为实际的对象。需要注意的是,readObject() 方法返回的是一个 Object 类型的引用,需要进行强制类型转换才能得到原始对象的引用。

  3. 对读取的对象进行操作和使用,如调用对象的方法、访问对象的属性等。

  4. 关闭 ObjectInputStream 对象和相关的输入流。

反序列化过程中可能会抛出 ClassNotFoundExceptionInvalidClassExceptionIOException 等异常,因此需要进行异常处理。

反序列化过程:通过 ObjectInputStream 类将字节流转换为对象进行反序列化。例如:

// 将字节流反序列化为对象
ObjectInputStream in = new  ObjectInputStream(new FileInputStream("data.ser"));
Object obj = in.readObject();
in.close();

反序列化过程会调用对象的构造方法来创建新的对象实例,并且不会触发类的静态代码块。此外,如果对象中定义了 writeObject()readObject() 方法,可以通过这两个方法实现对序列化和反序列化的自定义处理。

Kryo

实际开发中,很少使用 JDK 自带的序列化和反序列化,这是因为:

  • 可移植性差:Java 特有的,无法跨语言进行序列化和反序列化。

  • 性能差:序列化后的字节体积大,增加了传输/保存成本。

  • 安全问题:攻击者可以通过构造恶意数据来实现远程代码执行,从而对系统造成严重的安全威胁。

    相关阅读:Java 反序列化漏洞之殇

Kryo 是一个优秀的 Java 序列化和反序列化库,具有高性能、高效率和易于使用和扩展等特点,有效地解决了 JDK 自带的序列化机制的痛点

已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛使用

GitHub 地址:https://github.com/EsotericSoftware/kryo

使用示例

第一步,在 pom.xml 中引入依赖。

<!-- 引入 Kryo 序列化工具 -->
<dependency>
     <groupId>com.esotericsoftware</groupId>
     <artifactId>kryo</artifactId>
     <version>5.4.0</version>
</dependency>

第二步,创建一个 Kryo 对象,并使用 register() 方法将对象进行注册。然后,使用 writeObject() 方法将 Java 对象序列化为二进制流,再使用 readObject() 方法将二进制流反序列化为 Java 对象。最后,输出反序列化后的 Java 对象。

![[Kryo Example.png]]

序列化接口 Serializable

Java 序列化是 JDK 1.1 时引入的一组开创性的特性,用于将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态。

序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;

反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。

序列化有一条规则,就是要序列化的对象必须实现 Serializbale 接口,否则就会报 NotSerializableException 异常。

Serializable

Serializable 是 Java 标准库提供的接口,用于支持对象的序列化和反序列化操作。当一个类实现了 Serializable 接口,就意味着该类的对象可以被序列化为字节流,以便在网络传输或存储到本地文件系统等场景中使用

要实现 Serializable 接口,只需在类的声明中添加关键字 “implements Serializable”,并确保类的所有成员变量也是可序列化的。

这意味着类的所有成员变量要么是原始类型(如 int,double 等),要么是实现了 Serializable 接口的对象。如果类中的成员变量不是可序列化的,则需要标记为 transient,表示在序列化过程中忽略该成员变量。

序列化过程通过 ObjectOutputStream 类的 writeObject() 方法实现,可以将对象转换为字节流。反序列化过程通过 ObjectInputStream 类的 readObject() 方法实现,可以将字节流还原为一个对象。

Serializable 接口的存在使得 Java 对象可以在不同的虚拟机和操作系统之间进行传输和共享,它在分布式系统、网络通信和持久化存储等场景中很常见。然而,需要注意的是,对于一些敏感的数据,可能需要额外的安全措施来保护序列化和反序列化的过程。

字段序列化

statictransient 修饰的字段是不会被序列化的。

  • 序列化保存的是对象的状态,而 static 修饰的字段属于类的状态,因此可以证明序列化并不保存 static 修饰的字段。

  • transient (临时的),它可以阻止字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如 int 型的初始值为 0,对象型的初始值为 null

序列化接口 Externalizable

ExternalizableSerializable 的子接口

实现 Externalizable 接口的类 和 实现 Serializable 接口的类有一些不同:

  1. 新增了一个无参的构造方法。

    使用 Externalizable 进行反序列化的时候,会调用被序列化类的无参构造方法去创建一个新的对象,然后再将被保存对象的字段值复制过去。否则的话,会抛出异常。

  2. 新增了两个方法 writeExternal()readExternal(),实现 Externalizable 接口所必须的,以此来手动完成序列化和反序列化的过程,而 Serializable 接口不需要实现任何方法。

    transientExternalizable 中不起作用。若未重写以上两个方法,反序列化后得到的对象字段都变成了默认值,也就是说,序列化之前的对象状态没有被“冻结”下来。

Externalizable 接口提供了更高的序列化控制能力,可以在序列化和反序列化过程中对对象进行自定义的处理,如对一些敏感信息进行加密和解密。

序列化 ID

Java 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还有一个非常重要的因素就是序列化 ID 是否一致

serialVersionUID 被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。

在反序列化时,Java 虚拟机会把字节流中的 serialVersionUID 与被序列化类中的 serialVersionUID 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。

异常堆栈信息里面告诉我们,从持久化文件里面读取到的序列化 ID 和本地的序列化 ID 不一致,无法反序列化。

当一个类实现了 Serializable 接口后,该类最好生成一个序列化 ID

生成序列化 ID 的方法

  1. 添加一个默认版本的序列化 ID:
private static final long serialVersionUID = 1L

如果没有特殊需求,采用默认的序列化 ID(1L)就可以,也可以确保代码一致时反序列化成功。

  1. 添加一个随机生成的不重复的序列化 ID。
private static final long serialVersionUID = -2095916884810199532L;

若在序列化和反序列化的过程中更改了 序列化ID,会导致 JVM 抛出序列化版本不一致的异常

  1. 添加 @SuppressWarnings 注解。
@SuppressWarnings("serial")

使用 @SuppressWarnings("serial") 注解时,该注解会为被序列化类自动生成一个随机的序列化 ID,无需再手动设置。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【mysql】mysql之索引学习

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

面试搜狐大型模型算法工程师,感受非凡体验!

搜狐大模型算法工程师面试题 应聘岗位&#xff1a;搜狐大模型算法工程师 面试轮数&#xff1a; 整体面试感觉&#xff1a;偏简单 面试过程回顾 1. 自我介绍 在自我介绍环节&#xff0c;我清晰地阐述了个人基本信息、教育背景、工作经历和技能特长&#xff0c;展示了自信和沟通…

【Office】激活文件无法打开-DragonKMS--解决办法

【解决办法】右键 文件属性>>最下面勾选解除锁定即可打开。 【原因】&#xff1a;网络上下载的文件&#xff08;包括exe、zip等&#xff09;。

vue.js3+element-plus+typescript add,edit,del,search

vite.config.ts server: {cors: true, // 默认启用并允许任何源host: 0.0.0.0, // 这个用于启动port: 5110, // 指定启动端口open: true, //启动后是否自动打开浏览器 proxy: {/api: {target: http://localhost:8081/, //实际请求地址&#xff0c;数据库的rest APIschangeOr…

esp32 控制 st7735s 显示屏(spi)

Lcd初始化后全屏为花屏&#xff0c;必须再把整个屏幕转成全底白色消除花屏后再显示图片&#xff0c;字符。 我理解为什么是花屏&#xff0c;因为只是初始化各个参数&#xff0c;显示内存现在还是为空&#xff0c;还没有执行0x2c命令。 图片 #include "driver/spi_master…

统一 transformer 与 diffusion !Meta 融合新方法剑指下一代多模态王者

本文引入了 Transfusion&#xff0c;这是一种可以在离散和连续数据上训练多模态模型的方法。 来源丨机器之心 一般来说&#xff0c;多模态生成模型需要能够感知、处理和生成离散元素&#xff08;如文本或代码&#xff09;和连续元素&#xff08;如图像、音频和视频数据&#xf…

软件测试-Selenium+python自动化测试

目录 一、元素定位 1.1一个简单的模板 1.2单选框radio定位实战 1.3下拉操作 1.4弹窗 1.5文件上传 1.6 iframe(类似于页中页,嵌套进去了) 二、元素定位实战 会用到谷歌浏览器Chrome测试,需要下载一个Chromedriver(Chrome for Testing availability)对应自己的浏览…

力扣面试经典算法150题:除自身以外数组的乘积

除自身以外数组的乘积算法详解 今天的题目是力扣面试经典150题中的数组的中等难度题&#xff1a;除自身以外数组的乘积。 题目链接&#xff1a;https://leetcode.cn/problems/product-of-array-except-self/description/?envTypestudy-plan-v2&envIdtop-interview-150 …

docker基础到进阶

基础 文章目录 基础1.Docker简介2.Docker基础概念3.Docker安装4.Docker命令4.1 镜像命令4.2 容器命令 5. 数据卷5.1具名挂载5.2 匿名挂载 进阶1. 镜像5.2 Dockerfile5.3 网络1.网络模式2.网络操作 DockerCompose1.基本语法 总结 这篇文章记录了以下的内容&#xff1a; 1️⃣ 利…

达梦数据库的系统视图v$object_usage

达梦数据库的系统视图v$object_usage 在达梦数据库&#xff08;DM Database&#xff09;中&#xff0c;V$OBJECT_USAGE 视图提供了关于数据库对象的使用情况和统计信息。这些对象可以包括表、索引、视图、存储过程等。通过 V$OBJECT_USAGE 视图&#xff0c;数据库管理员可以监…

如何使用Hive构建网络电视剧收视率分析系统:大数据实战教程

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

什么?入网小助手无法卸载?我来手把手教你

简介 之前装了 XXXXX 公司的入网小助手&#xff0c;卸载的时候发现要验证码&#xff0c;我这小脾气就上来了&#xff0c;对待流氓软件是可忍熟不可忍&#xff01;&#xff01;&#xff01; 这里介绍的只是一种方式&#xff0c;嫌麻烦的可以进入安全模式直接删除安装目录内容&…

操作系统----锁

锁 锁就是一个变量 为什么需要锁&#xff1f;&#xff1a;&#xff1a;需要原子性的执行一系列的操作指令&#xff0c;程序员在源代码中加锁&#xff0c;放在临界区周围&#xff0c;保证临界区能够像单条原子指令一样执行。 举例说明 锁&#xff08;通常是一个互斥量mutex&…

大数据5v特性、集群、分布式

目录 数据分析六部曲 大数据的特点 &#xff08;5v特征&#xff09; 分布式与集群的区别 常用的分布式方案 数据分析六部曲 明确分析目的和思路&#xff1a;确保分析框架的体系化和逻辑性&#xff0c;简单来说就是先分析什么&#xff0c;后分析什么&#xff0c;使得各个分析…

K-means算法原理及应用场景

1. 算法原理 K-means是一种广泛使用的聚类算法&#xff0c;其目标是将数据点划分为K个簇&#xff0c;使得簇内的点尽可能地接近簇中心&#xff08;质心&#xff09;&#xff0c;而簇间的点则尽可能地远离。算法的核心思想是最小化簇内的平方误差。 过程&#xff1a; 初始化&a…

命题的相关知识

一、推论 推论由前提和结论两部分构成。前提和结论部分都是命题。 命题是推论的基本单位 命题特点&#xff1a;1、是陈述句 2、有确定的值&#xff08;不是对就是错&#xff09; 知不知道真假不重要&#xff0c;有真假就行 连接词和简单命题组成了复合命题。 二、…

前端:html+css:伪类画箭头(实心)

一、效果图 二、代码 html <div class"rectangle">AC/DC</div> css /* 图形 */ .rectangle {position: relative;width: 50px;height: 20px;background-color: #3498db;color: white; } .rectangle:before {content: ;position: absolute;top: 0;l…

Python-进阶-Excel基本操作

文章目录 Excel 基本操作1. 概述2. 写入2.1 使用 xlwt2.2 使用 XlsxWriter 3. 读取4. 修改 Excel 基本操作 1. 概述 在数据处理方面&#xff0c;Python 一直扮演着重要的角色&#xff0c;对于 Excel 操作&#xff0c;它有着完整且成熟的第三方库&#xff0c;使用也较为简单。…

用IP代理网速过慢:原因分析与解决方案

使用IP代理时网速过慢的原因及解决方案 在现代互联网环境中&#xff0c;使用IP代理已成为保护隐私的常见手段。然而&#xff0c;许多用户在使用代理时常常会遇到网速过慢的问题&#xff0c;这不仅影响了使用体验&#xff0c;还可能导致工作效率下降。本文将探讨导致IP代理网速…

Cornerstone3D Tools对影像进行交互(上篇)-基础交互工具及同步器

⛳️ 前言 在我们日常需求中&#xff0c;除了需要对影像进行可视化展示外&#xff0c;大多数场景下还需要对影像进行调整、注释、分割等操作。Cornerstone3DTools库则支持大多数需要的交互功能。CornerstoneTools支持的工具类型主要分为以下4类&#xff1a; 基础交互类工具&am…