Java安全 | 序列化与反序列化总结

2025-01-10 2 0

一、序列化和反序列化

1、基本概念

序列化:将对象转换为字节流。

反序列化:将字节流转换回对象。

Java 提供了Serializable接口来标识一个类可以被序列化,使用ObjectOutputStream和ObjectInputStream来进行序列化和反序列化。

示例:

import java.io.*;

class Person implements Serializable {
private final String name;
private final int age;

// 构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getter 和 setter 方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
// 输出对象信息
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + '}';
}


}

public class SerializationExample {

public static void main(String[] args) {
try {
// 创建一个 Person 对象
Person person1 = new Person("小明", 20);

// 序列化:将对象转换成字节流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(person1);
objectOutputStream.flush();

// 获取字节流
byte[] byteData = byteArrayOutputStream.toByteArray();

// 输出序列化后的字节数据
System.out.println("序列化后的字节数据: ");
for (byte b : byteData) {
System.out.print(b + " ");
}
System.out.println();

// 反序列化:将字节流转换回对象
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteData);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Person person2 = (Person) objectInputStream.readObject();

// 输出反序列化后的对象
System.out.println("原始对象: " + person1 + " hashCode: " + System.identityHashCode(person1));
System.out.println("反序列化后的对象: " + person2 + " hashCode: " + System.identityHashCode(person2));

} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

执行结果:

序列化后的字节数据: 
-84 -19 0 5 115 114 0 14 99 111 109 46 115 101 114 46 80 101 114 115 111 110 -32 -13 39 118 58 -4 37 105 2 0 2 73 0 3 97 103 101 76 0 4 110 97 109 101 116 0 18 76 106 97 118 97 47 108 97 110 103 47 83 116 114 105 110 103 59 120 112 0 0 0 20 116 0 6 -27 -80 -113 -26 -104 -114 
原始对象: Person{name='小明', age=20} hashCode: 764308918
反序列化后的对象: Person{name='小明', age=20} hashCode: 2114664380

在输出时也把原始对象和序列化后对象的hashcode(内存地址)输出出来了,主要是想在这里告诉大家,虽然原始对象和序列化后对象的内容是相同的,但本质上不同。

2、原始对象和反序列化后的对象区别

在序列化和反序列化过程中,原始对象和反序列化后的对象的区别主要体现在它们的“内存地址”和“对象身份”的不同,而对象的内容通常是相同的。

原始对象:
内存地址:原始对象是程序运行时创建的对象实例,存储在堆内存中。当你打印原始对象时,实际是打印它的字段值。
对象身份:原始对象是通过 new 关键字创建的,每个对象在内存中都有唯一的标识(即内存地址),因此原始对象的标识和反序列化后的对象不同。
生命周期:原始对象通常是在内存中创建和操作的,直到它被垃圾回收。

反序列化后的对象:
内存地址:反序列化后的对象是从字节流恢复出来的,它在内存中的位置和原始对象的内存地址不同。即使两个对象的字段值完全相同,它们在内存中的位置不同,所以它们的内存地址也是不同的。
对象身份:通过反序列化恢复的对象是一个全新的实例,尽管其字段值与原始对象相同,但它是一个不同的对象,拥有不同的内存地址。
生命周期:反序列化后的对象的生命周期由 JVM 管理,它会在不再使用时被垃圾回收。

3、serialVersionUID

serialVersionUID 是一个序列化版本标识符,它帮助 Java 确保序列化和反序列化过程中对象的类版本兼容。如果在序列化对象时保存了 serialVersionUID,当进行反序列化时,Java 会检查反序列化的类和原来序列化时的类是否具有相同的 serialVersionUID,以此来验证类的兼容性。如果不相同,反序列化时会抛出 InvalidClassException 异常。

为什么需要serialVersionUID?

1、当一个类被序列化时,它的 serialVersionUID 会被保存在序列化数据中。
2、当尝试反序列化时,JVM 会读取这个 serialVersionUID,然后检查目标类是否有相同的 serialVersionUID。
3、如果没有显式声明 serialVersionUID,Java 会根据类的内部结构自动计算该 ID,这样不同的类变更可能会导致生成不同的 serialVersionUID,从而无法兼容之前的版本。

声明方法

private static final long serialVersionUID = 1L;

4、transient  static  final 

transient

transient 关键字标记的字段在序列化过程中会被忽略,不会被序列化或反序列化。当一个字段被声明为 transient,它的值不会被序列化。反序列化时,这些字段将被赋予其类型的默认值。

示例:

import java.io.Serializable;

public class Person implements Serializable {
private String name;
private transient int age; // 不会被序列化

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

// Getter and Setter
}

static

static 修饰的字段是类级别的,而不是对象级别的。因此,它们不参与序列化过程。静态字段是与类相关的,而不是与实例相关的,它们不会随着对象的序列化一起存储。

示例:

import java.io.Serializable;

public class Person implements Serializable {
private String name;
private static int count = 0; // 不会被序列化

public Person(String name) {
this.name = name;
count++;
}

// Getter and Setter
}

final 

常量字段(final 字段)在序列化时是被序列化的,但它的值在反序列化时不一定需要被修改。通常,对于一个 final 字段,Java 会假设其值在整个对象的生命周期内是固定的,因此,反序列化时,这些字段的值不会被重新赋值。但这并不意味着它不会被序列化。final 字段在序列化时是存储的,但在反序列化时,JVM 会尝试使用原来的值进行初始化。

5、Externalizable接口

在Java中除了Serializable接口可以进行序列化之外,Externalizable接口同样也可以。Externalizable 是 Serializable 接口的一个子接口,它提供了更多的控制权,允许开发者自定义序列化和反序列化的过程。与 Serializable 接口不同,Externalizable 需要实现两个方法:writeExternal 和 readExternal,这两个方法可以控制对象如何序列化和如何反序列化。

将上面的例子用Externalizable接口表示:

import java.io.*;

class Person implements Externalizable {
private String name;
private int age;

// 默认构造函数是必需的,因为 Externalizable 需要它
public Person() {
// 必须存在无参构造器
}

// 带参数的构造函数
public Person(String name, int age) {
this.name = name;
this

4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

海外数据存储与跨境传输-给中小企业的一些实操思路
【Web逆向】修改浏览器的C++代码,定制随机指纹
Group3r:一款针对活动目录组策略安全的漏洞检测工具
代码审计 | JTop CMS 目录穿越漏洞分析
Exegol:一款功能完整的社区驱动型渗透测试工具
代码审计 | oasys 任意文件读取漏洞分析

发布评论