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

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

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 setterspublic String getName() { return name; }public int getAge() { return age; }@Overridepublic 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 setterspublic String getUsername() { return username; }public String getPassword() { return password; }@Overridepublic 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;}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(name);out.writeInt(employeeId);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {name = (String) in.readObject();employeeId = in.readInt();}@Overridepublic 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);// 对象转JSONString 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.xdnf.cn/news/927289.html

相关文章:

  • 基于ANN-GA优化鲜切萝卜杀菌工艺参数
  • GICv3-PMU
  • 树莓派远程登陆RealVNC Viewer出现卡顿
  • 基于51单片机的多功能风扇控制系统
  • 判断软件是否安装,如果没有则自动安装
  • 声音信号的基频检测(python版本)
  • C++学习思路
  • DL00335-基于深度学习YOLOv11的煤矸石检测含完整数据集
  • [逆向工程] C实现过程调试与钩子安装(二十七)
  • 关于datetime获取时间的问题
  • 顶级创新者在人机互助中成为关键乘数(而并非简单地加数)
  • can转Profinet网关转换:S7-1200PLC与施耐德变频器间的通信实现
  • 测试工程师的AI测试开发进阶:LangChain在多测试领域的实战与思考
  • Go 语言 range 关键字全面解析
  • 如何从浏览器中导出网站证书
  • 蓝牙音乐(A2DP)音频延迟的一些感想跟分析,让你对A2DP体验更佳深入
  • Win11打开应用程序会弹出“打开文件-安全警告“弹框
  • Linux实战篇、第一章_02若依前后端部署之路(前端)
  • 基于51单片机的光强调节LED亮度
  • DAY 44 预训练模型
  • SD模型部署
  • 微服务架构详解:从入门到实战
  • Codeforces Round 1025 (Div. 2) B. Slice to Survive
  • PCB有铜半孔工艺——高密度电子连接的“隐形桥梁”
  • 能 ping 通网址,但是网页打不开
  • 嵌入式知识篇---Zigbee串口
  • 基于51单片机的光强控制LED灯亮灭
  • C++11 Token Bucket (令牌桶)算法的锁无实现及应用
  • 《前缀和》题集
  • 0基础破解Typora,使用正版已激活Typora