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

java序列化

Java 序列化是将对象的状态转换为字节流的过程,以便可以将其存储在文件、数据库中,或通过网络传输;而反序列化则是将字节流恢复为对象的过程。这一机制在分布式系统、缓存、持久化等场景中广泛应用。

一、序列化的基本概念

  • 序列化(Serialization):把对象转换为字节序列的过程。
  • 反序列化(Deserialization):把字节序列恢复为对象的过程。
  • 核心用途:对象的持久化存储、跨网络传输对象。

二、实现序列化的条件

要让一个类可序列化,需满足以下条件:

  1. 类必须实现 java.io.Serializable 接口(这是一个标记接口,无任何方法)。
  2. 类的所有非静态成员变量必须是可序列化的(如果有不可序列化的成员,需用 transient 关键字修饰)。
  3. 若类有父类,父类要么可序列化,要么有默认无参构造方法(否则反序列化会报错)。

三、核心类与方法

Java 提供了 ObjectOutputStream 和 ObjectInputStream 来处理序列化和反序列化:

  1. ObjectOutputStream

    • 用于序列化对象,核心方法:
      writeObject(Object obj)  // 将对象写入输出流
      
  2. ObjectInputStream

    • 用于反序列化对象,核心方法:
      readObject()  // 从输入流读取对象并返回
      

四、基本使用示例

import java.io.*;// 可序列化的类
class Person implements Serializable {private String name;private int age;// transient 修饰的变量不会被序列化private transient String password;public Person(String name, int age, String password) {this.name = name;this.age = age;this.password = password;}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + ", password='" + password + "'}";}
}public class SerializationDemo {public static void main(String[] args) {// 序列化try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {Person person = new Person("Alice", 30, "123456");oos.writeObject(person);System.out.println("序列化完成");} catch (IOException e) {e.printStackTrace();}// 反序列化try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {Person deserializedPerson = (Person) ois.readObject();System.out.println("反序列化结果: " + deserializedPerson);// 输出:Person{name='Alice', age=30, password='null'}// 可见 password 因 transient 未被序列化} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}
}

五、serialVersionUID 的作用

serialVersionUID 是一个静态常量,用于标识类的版本。其作用是:

  • 序列化时,JVM 会将类的 serialVersionUID 写入字节流。
  • 反序列化时,JVM 会检查字节流中的 serialVersionUID 与当前类的是否一致:
    • 一致:正常反序列化。
    • 不一致:抛出 InvalidClassException

建议显式声明

class Person implements Serializable {// 显式声明 serialVersionUIDprivate static final long serialVersionUID = 1L;// ... 其他成员
}

若不声明,JVM 会根据类的结构(成员变量、方法等)自动生成,但类结构的微小变化(如增加字段)都会导致 serialVersionUID 变化,从而破坏兼容性。

六、transient 关键字

  • 被 transient 修饰的成员变量不会被序列化,反序列化时会被赋予默认值(如 null0)。
  • 常用于敏感信息(如密码)或不需要持久化的临时变量。

七、自定义序列化与反序列化

通过重写以下方法,可自定义序列化 / 反序列化逻辑:

// 自定义序列化逻辑
private void writeObject(ObjectOutputStream out) throws IOException {// 先执行默认序列化out.defaultWriteObject();// 自定义处理(如加密敏感字段)out.writeObject(encrypt(password)); 
}// 自定义反序列化逻辑
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {// 先执行默认反序列化in.defaultReadObject();// 自定义处理(如解密)password = decrypt((String) in.readObject());
}// 示例加密方法(简化)
private String encrypt(String str) {return str + "_encrypted";
}private String decrypt(String str) {return str.replace("_encrypted", "");
}

八、单例模式与序列化

默认情况下,序列化会破坏单例(反序列化会创建新对象)。解决方法:重写 readResolve() 方法:

class Singleton implements Serializable {private static final long serialVersionUID = 1L;private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}// 确保反序列化返回唯一实例private Object readResolve() {return instance;}
}

九、序列化的限制与注意事项

  1. 不可序列化的类型

    • 某些原生类型(如 ThreadSocket)不可序列化。
    • 被 transient 修饰的成员不可序列化。
  2. 安全性问题

    • 反序列化可能存在安全风险(如恶意构造字节流执行恶意代码),需谨慎处理不可信数据。
    • Java 9+ 引入了序列化过滤机制(ObjectInputFilter)来增强安全性。
  3. 性能影响

    • 序列化会产生额外的字节流开销,频繁序列化可能影响性能。
    • 可考虑更高效的序列化框架(如 Protobuf、Kryo)替代原生序列化。
  4. 版本兼容性

    • 类结构变化需谨慎,建议通过 serialVersionUID 控制版本。
    • 新增字段时,反序列化旧版本数据会使用默认值;删除字段时,旧版本数据中的对应字段会被忽略。

十、常见异常

  • NotSerializableException:类未实现 Serializable 接口。
  • InvalidClassExceptionserialVersionUID 不匹配或类结构不兼容。
  • ClassNotFoundException:反序列化时找不到对应的类。

总结

Java 序列化是对象持久化和网络传输的基础机制,通过实现 Serializable 接口、合理使用 serialVersionUID 和 transient 关键字,以及自定义序列化逻辑,可以灵活应对各种场景。但需注意其安全性和性能问题,在分布式系统中可考虑更高效的序列化方案。

http://www.xdnf.cn/news/1395631.html

相关文章:

  • Android系统框架知识系列(十九):Android安全架构深度剖析 - 从内核到应用的全栈防护
  • python学习打卡day48
  • “白月光”焦点何晟铭现身宁夏中宁,助力非遗与三农发展
  • 拎包入住搭建 Browser Use Agent:基于PPIO Model API +Agent 沙箱的一体化构建
  • 变量声明方式
  • linux学习-数据库
  • 中科米堆CASAIM五金配件三维扫描测量尺寸形位公差
  • 嵌入式Linux驱动开发:i.MX6ULL平台设备驱动
  • 使用 Docker 部署 Squid 为 Kubernetes 中的 Nexus3 提供公网代理访问
  • linux 条件变量与生产消费者模型
  • 玳瑁的嵌入式日记D29-0829(进程间通信)
  • Python OpenCV图像处理与深度学习:Python OpenCV开发环境搭建与入门
  • 基于能量方法的纳维-斯托克斯方程高阶范数有界性理论推导-陈墨仙
  • STM32CubeMX + HAL 库:基于 I²C 通信的 AHT20 高精度温湿度测量实验
  • 【系列03】端侧AI:构建与部署高效的本地化AI模型 第2章:端侧AI硬件入门
  • 134-细粒度多尺度符号熵和鲸鱼优化算法的滚动轴承故障诊断技术MSVM
  • Redis搭建哨兵模式一主两从三哨兵
  • 线程安全及死锁问题
  • 【好题推荐】运算符的构造运用
  • 光伏发多少电才够用?匹配家庭用电需求
  • #医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(五)
  • Linux内核进程管理子系统有什么第三十八回 —— 进程主结构详解(34)
  • JUC并发编程09 - 内存(01) - JMM/cache
  • 嵌入式Linux设备树驱动开发 - dtsof驱动
  • Unity DateTime 相关
  • 处理器(CPU/MPU)的双发射是什么?
  • 命令扩展与重定向
  • 可解释人工智能XAI
  • 【机器学习深度学习】Embedding 与 RAG:让 AI 更“聪明”的秘密
  • leetcode 191 位1的个数