Java-IO流之序列化与反序列化详解

news2025/6/9 4:58:06

Java-IO流之序列化与反序列化详解

    • 一、序列化与反序列化概述
      • 1.1 基本概念
      • 1.2 核心接口与类
      • 1.3 应用场景
    • 二、Java序列化的基本实现
      • 2.1 实现Serializable接口
      • 2.2 使用ObjectOutputStream进行序列化
      • 2.3 使用ObjectInputStream进行反序列化
    • 三、序列化的高级特性
      • 3.1 serialVersionUID的作用
      • 3.2 瞬态关键字transient
      • 3.3 自定义序列化方法
      • 3.4 实现Externalizable接口
    • 四、序列化的最佳实践
      • 4.1 始终声明serialVersionUID
      • 4.2 处理敏感字段
      • 4.3 实现readObjectNoData方法
      • 4.4 序列化单例和枚举
      • 4.5 序列化集合和数组
    • 五、序列化的常见问题与解决方案
      • 5.1 NotSerializableException
      • 5.2 InvalidClassException
      • 5.3 性能问题
      • 5.4 安全风险
    • 六、替代序列化方案
      • 6.1 JSON序列化
      • 6.2 Protobuf序列化
      • 6.3 Kryo序列化

Java中对象的序列化(Serialization)与反序列化(Deserialization)是一项重要技术,为对象的持久化和远程传输提供了基础支持,它允许将对象转换为字节流以便存储或传输,也可以将字节流还原为原始对象。这项技术在分布式系统、远程方法调用(RMI)、缓存机制等场景中有着广泛的应用。本文我将深入探讨Java序列化与反序列化的原理、实现方法及最佳实践,带你全面掌握这一核心技术。

一、序列化与反序列化概述

1.1 基本概念

  • 序列化(Serialization):将Java对象转换为字节流的过程
  • 反序列化(Deserialization):将字节流恢复为Java对象的过程

1.2 核心接口与类

  • Serializable接口:标记接口,实现该接口的类可以被序列化
  • Externalizable接口:继承自Serializable,提供更细粒度的序列化控制
  • ObjectOutputStream:用于将对象写入输出流
  • ObjectInputStream:用于从输入流读取对象

1.3 应用场景

  • 对象持久化:将对象保存到文件或数据库
  • 远程通信:在网络中传输对象
  • 缓存机制:将对象缓存到内存或磁盘
  • 分布式系统:在不同节点间传递对象

二、Java序列化的基本实现

2.1 实现Serializable接口

要使一个类可序列化,只需实现java.io.Serializable接口(该接口是一个标记接口,没有方法)。

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // Getters and setters
    public String getName() { return name; }
    public int getAge() { return age; }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

2.2 使用ObjectOutputStream进行序列化

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        Person person = new Person("张三", 30);
        
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("person.ser"))) {
            
            // 序列化对象
            oos.writeObject(person);
            System.out.println("对象序列化成功");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.3 使用ObjectInputStream进行反序列化

import java.io.*;

public class DeserializationExample {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("person.ser"))) {
            
            // 反序列化对象
            Person person = (Person) ois.readObject();
            System.out.println("对象反序列化成功");
            System.out.println(person);
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

三、序列化的高级特性

3.1 serialVersionUID的作用

serialVersionUID是一个标识序列化类版本的静态常量,用于在反序列化时验证类的兼容性。如果不指定,Java会根据类的结构自动生成一个,但建议显式声明以避免版本不一致问题。

private static final long serialVersionUID = 1L;

3.2 瞬态关键字transient

使用transient关键字修饰的字段不会被序列化,反序列化后该字段的值为默认值。

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String username;
    private transient String password; // 密码字段不被序列化
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    // Getters and setters
    public String getUsername() { return username; }
    public String getPassword() { return password; }
    
    @Override
    public String toString() {
        return "User{username='" + username + "', password='" + password + "'}";
    }
}

3.3 自定义序列化方法

可以通过在类中定义以下两个特殊方法来自定义序列化过程:

  • private void writeObject(ObjectOutputStream out)
  • private void readObject(ObjectInputStream in)
import java.io.*;

public class CustomSerialization implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private int value;
    
    public CustomSerialization(int value) {
        this.value = value;
    }
    
    // 自定义序列化方法
    private void writeObject(ObjectOutputStream out) throws IOException {
        // 写入原始值的加密版本
        out.writeInt(value * 2);
    }
    
    // 自定义反序列化方法
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        // 读取加密值并解密
        value = in.readInt() / 2;
    }
    
    public int getValue() {
        return value;
    }
}

3.4 实现Externalizable接口

Externalizable接口继承自Serializable,提供了更细粒度的序列化控制。实现该接口需要重写writeExternalreadExternal方法。

import java.io.*;

public class Employee implements Externalizable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int employeeId;
    
    // 必须提供无参构造函数
    public Employee() {}
    
    public Employee(String name, int employeeId) {
        this.name = name;
        this.employeeId = employeeId;
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(employeeId);
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        employeeId = in.readInt();
    }
    
    @Override
    public String toString() {
        return "Employee{name='" + name + "', employeeId=" + employeeId + "}";
    }
}

四、序列化的最佳实践

4.1 始终声明serialVersionUID

为每个可序列化的类显式声明serialVersionUID,以确保版本兼容性。

4.2 处理敏感字段

使用transient关键字标记敏感字段,避免在序列化过程中泄露信息。

4.3 实现readObjectNoData方法

在类中实现readObjectNoData()方法,以处理反序列化时没有数据的情况。

private void readObjectNoData() throws ObjectStreamException {
    // 初始化对象的默认状态
    this.name = "默认名称";
    this.age = 0;
}

4.4 序列化单例和枚举

对于单例类,应确保反序列化不会创建新的实例,可以通过实现readResolve()方法:

private Object readResolve() throws ObjectStreamException {
    return Singleton.getInstance();
}

枚举类型本身就支持序列化,无需特殊处理。

4.5 序列化集合和数组

集合和数组如果包含可序列化的元素,则它们本身也是可序列化的。

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class CollectionSerializationExample {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("张三", 30));
        people.add(new Person("李四", 40));
        
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("people.ser"))) {
            
            oos.writeObject(people);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("people.ser"))) {
            
            @SuppressWarnings("unchecked")
            List<Person> deserializedPeople = (List<Person>) ois.readObject();
            
            for (Person person : deserializedPeople) {
                System.out.println(person);
            }
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

五、序列化的常见问题与解决方案

5.1 NotSerializableException

当尝试序列化未实现Serializable接口的类时,会抛出此异常。解决方案是确保所有需要序列化的类都实现Serializable接口。

5.2 InvalidClassException

当序列化版本不一致或类结构发生变化时,可能会抛出此异常。解决方案是显式声明serialVersionUID,并在类结构变化时谨慎处理。

5.3 性能问题

Java原生序列化性能较低,特别是对于大量数据。可以考虑使用更高效的序列化框架,如JSON、Protobuf、Kryo等。

5.4 安全风险

反序列化不受信任的数据可能导致安全漏洞,如远程代码执行。应避免反序列化来自不可信源的数据,或使用安全的序列化框架。

六、替代序列化方案

6.1 JSON序列化

JSON是一种轻量级的数据交换格式,广泛用于Web应用中。可以使用Jackson、Gson等库实现Java对象与JSON的互转。

import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonSerializationExample {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person("张三", 30);
        
        // 对象转JSON
        String json = mapper.writeValueAsString(person);
        System.out.println("JSON: " + json);
        
        // JSON转对象
        Person deserializedPerson = mapper.readValue(json, Person.class);
        System.out.println("对象: " + deserializedPerson);
    }
}

6.2 Protobuf序列化

Protobuf是Google开发的高效序列化框架,具有高性能和小体积的特点。

// 定义.proto文件
syntax = "proto3";

message Person {
    string name = 1;
    int32 age = 2;
}

// 使用Protobuf生成的类进行序列化和反序列化
PersonProto.Person person = PersonProto.Person.newBuilder()
        .setName("张三")
        .setAge(30)
        .build();

// 序列化
byte[] data = person.toByteArray();

// 反序列化
PersonProto.Person deserializedPerson = PersonProto.Person.parseFrom(data);

6.3 Kryo序列化

Kryo是一个快速高效的Java序列化框架,支持自定义序列化策略。

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

public class KryoSerializationExample {
    public static void main(String[] args) {
        Kryo kryo = new Kryo();
        kryo.register(Person.class);
        
        Person person = new Person("张三", 30);
        
        // 序列化
        try (Output output = new Output(new FileOutputStream("person.kryo"))) {
            kryo.writeObject(output, person);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 反序列化
        try (Input input = new Input(new FileInputStream("person.kryo"))) {
            Person deserializedPerson = kryo.readObject(input, Person.class);
            System.out.println(deserializedPerson);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

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

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

相关文章

CAN通信收发测试(USB2CAN模块测试实验)

1.搭建测试环境 电脑&#xff1a;安装 USB 驱动&#xff0c;安装原厂调试工具&#xff0c;安装cangaroo&#xff08;参考安装包的入门教程即可&#xff09; USB驱动路径&#xff1a;~\CAN分析仪资料20230701_Linux\硬件驱动程序 原厂调试工具路径&#xff1a;~\CAN分析仪资料2…

OSCP备战-BSides-Vancouver-2018-Workshop靶机详细步骤

一、靶机介绍 靶机地址&#xff1a;https://www.vulnhub.com/entry/bsides-vancouver-2018-workshop%2C231/ 靶机难度&#xff1a;中级&#xff08;CTF&#xff09; 靶机发布日期&#xff1a;2018年3月21日 靶机描述&#xff1a; Boot2root挑战旨在创建一个安全的环境&…

PDF转Markdown/JSON软件MinerU最新1.3.12版整合包下载

MinerU发布至今我已经更新多版整合包了&#xff0c;5天前MinerU发布了第一个正式版1.0.1&#xff0c;并且看到在18小时之前有更新模型文件&#xff0c;我就做了个最新版的一键启动整合包。 2025年02月21日更新v1.1.0版整合包 2025年02月27日更新v1.2.0版整合包 2025-06-05 更…

【深入学习Linux】System V共享内存

目录 前言 一、共享内存是什么&#xff1f; 共享内存实现原理 共享内存细节理解 二、接口认识 1.shmget函数——申请共享内存 2.ftok函数——生成key值 再次理解ftok和shmget 1&#xff09;key与shmid的区别与联系 2&#xff09;再理解key 3&#xff09;通过指令查看/释放系统中…

编程基础:执行流

能帮到你的话&#xff0c;就给个赞吧 &#x1f618; 文章目录 执行流同步&#xff1a;顺序执行&#xff0c;只有一个执行流异步&#xff1a;新开后台(次)执行流&#xff0c;后台执行流要确保不能影响主执行流。共有两个执行流。 阻塞&#xff1a;任务阻塞执行流&#xff0c;导致…

理解非结构化文档:将 Reducto 解析与 Elasticsearch 结合使用

作者&#xff1a;来自 Elastic Adel Wu 演示如何将 Reducto 的文档处理与 Elasticsearch 集成以实现语义搜索。 Elasticsearch 与业界领先的生成式 AI 工具和提供商有原生集成。欢迎观看我们的网络研讨会&#xff0c;了解如何超越 RAG 基础&#xff0c;或使用 Elastic 向量数据…

[面试精选] 0094. 二叉树的中序遍历

文章目录 1. 题目链接2. 题目描述3. 题目示例4. 解题思路5. 题解代码6. 复杂度分析 1. 题目链接 94. 二叉树的中序遍历 - 力扣&#xff08;LeetCode&#xff09; 2. 题目描述 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 3. 题目示例 示例 1 : 输入&…

学习笔记(23): 机器学习之数据预处理Pandas和转换成张量格式[1]

学习笔记(23): 机器学习之数据预处理Pandas和转换成张量格式[1] 学习机器学习&#xff0c;需要学习如何预处理原始数据&#xff0c;这里用到pandas&#xff0c;将原始数据转换为张量格式的数据。 1、安装pandas pip install pandas 2、写入和读取数据 >>创建一个人工…

2025年6月6日第一轮

2025年6月6日 The rapid in Chiese industdy is developnig e,and it is From be in a enjoy a deep is developing The drone industry in China is developing The drone industy in china develops rapidly and is in a leading position in in the world. The dro…

12-Oracle 23ai Vector 使用ONNX模型生成向量嵌入

一、Oracle 23ai Vector Embeddings 核心概念​ 向量嵌入&#xff08;Vector Embeddings&#xff09;​​ -- 将非结构化数据&#xff08;文本/图像&#xff09;转换为数值向量 - - 捕获数据的语义含义而非原始内容 - 示例&#xff1a;"数据库" → [0.24, -0.78, 0.5…

pytorch 与 张量的处理

系列文章目录 文章目录 系列文章目录一、Tensor 的裁剪二、Tensor 的索引与数据筛选torch.wheretorch.indicestorch.gathertorch.masked_selecttorch.taketorch.nonzero&#xff08;省略&#xff09; 三、Tensor 的组合与拼接torch.cattorch.stack 四、Tensor的切片chunksplit …

layer norm和 rms norm 对比

Layer norm # Layer Norm 公式 mean x.mean(dim-1, keepdimTrue) var x.var(dim-1, keepdimTrue) output (x - mean) / sqrt(var eps) * gamma beta特点&#xff1a; 减去均值&#xff08;去中心化&#xff09;除以标准差&#xff08;标准化&#xff09;包含可学习参数 …

Java高级 | 【实验六】Springboot文件上传和下载

隶属文章&#xff1a;Java高级 | &#xff08;二十二&#xff09;Java常用类库-CSDN博客 系列文章&#xff1a;Java高级 | 【实验一】Springboot安装及测试 |最新-CSDN博客 Java高级 | 【实验二】Springboot 控制器类相关注解知识-CSDN博客 Java高级 | 【实验三】Springboot 静…

VBA进度条ProgressForm1

上一章《VBA如何使用ProgressBar进度条控件》介绍了ProgressBar控件的使用方法&#xff0c;今天我给大家介绍ProgressForm1进度条的使用方法&#xff0c;ProgressForm1是集成ProgressBar控件和Label控件的窗体&#xff0c;可以同时显示进度条和百分比&#xff0c;如下图&#x…

行为型设计模式之Interpreter(解释器)

行为型设计模式之Interpreter&#xff08;解释器&#xff09; 前言&#xff1a; 自己的话理解&#xff1a;自定义一个解释器用来校验参数或数据是否合法。 1&#xff09;意图 给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;这个解…

深入解析 CAS 操作

一、CAS 的本质&#xff1a;硬件级别的乐观锁 CAS&#xff08;Compare-And-Swap&#xff0c;比较并交换&#xff09; 是一种原子操作指令&#xff0c;用于实现对共享变量的无锁并发修改。它是现代多核处理器支持的底层硬件指令&#xff0c;也是构建高效并发数据结构&#xff0…

vue3+TS+eslint9配置

记录eslint升级到9.x的版本之后遇到的坑 在 ESLint 9 中&#xff0c;配置方式发生了变化。Flat Config 格式&#xff08;eslint.config.js 或 .ts&#xff09;不再支持 extensions 选项。所以vscode编辑器中的 extensions 需要注释掉&#xff0c;要不然保存的时候不会格式化。…

【bug】Error: /undefinedfilename in (/tmp/ocrmypdf.io.9xfn1e3b/origin.pdf)

在使用ocrmypdf的时候&#xff0c;需要Ghostscript9.55及以上的版本&#xff0c;但是ubuntu自带为9.50 然后使用ocrmypdf报错了 sudo apt update sudo apt install ghostscript gs --version 9.50 #版本不够安装的版本为9.50不够&#xff0c;因此去官网https://ghostscript.c…

Redis :String类型

String类型 String是Redis中的字符串&#xff0c;是Redis中最基本的数据类型&#xff0c;直接是按照二进制数据的进行存储 Redis中的所有key都是String类型&#xff0c;但是value是有差别的 常见的命令 set 将String类型的value存储到key中&#xff0c;如果之间有相同的ke…

第18节 Node.js Web 模块

什么是 Web 服务器&#xff1f; Web服务器一般指网站服务器&#xff0c;是指驻留于因特网上某种类型计算机的程序。 Web服务器的基本功能就是提供Web信息浏览服务。它只需支持HTTP协议、HTML文档格式及URL&#xff0c;与客户端的网络浏览器配合。 大多数web服务器都支持服务…