招行2面:为什么需要序列化和反序列?为什么不能直接使用对象?

Hi,你好,我是猿java。

工作中,我们经常听到序列化反序列化,那么,什么是序列化?什么又是反序列化?这篇文章,我们来分析一个招商的面试题:为什么需要序列化反序列化

1. 什么是序列化和反序列化?

简单来说,序列化就是把一个Java对象转换成一系列字节的过程,这些字节可以被存储到文件、数据库,或者通过网络传输。反过来,反序列化则是把这些字节重新转换成Java对象的过程。

想象一下,你有一个手机应用中的用户对象(比如用户的名字、年龄等信息)。如果你想将这个用户对象存储起来,或者发送给服务器,你就需要先序列化它。等到需要使用的时候,再通过反序列化把它恢复成原来的对象。

2. 为什么需要序列化?

“为什么需要序列化?为什么不能直接使用对象呢?”这确实是一个好问题,而且很多工作多年的程序员不一定能回答清楚。综合来看:需要序列化的主要原因有以下三点:

  1. 持久化存储:当你需要将对象的数据保存到磁盘或数据库中时,必须把对象转换成一系列字节。
  2. 网络传输:在分布式系统中,不同的机器需要交换对象数据,序列化是实现这一点的关键。
  3. 深拷贝:有时候需要创建对象的副本,序列化和反序列化可以帮助你实现深拷贝。

更直白的说,序列化是为了实现持久化和网络传输,对象是应用层的东西,不同的语言(比如:java,go,python)创建的对象还不一样,实现持久化和网络传输的载体不认这些对象。

3. 序列化的原理分析

Java中的序列化是通过实现java.io.Serializable接口来实现的。这个接口是一个标记接口,意味着它本身没有任何方法,只是用来标记这个类的对象是可序列化的。

当你序列化一个对象时,Java会将对象的所有非瞬态(transient)和非静态字段的值转换成字节流。这包括对象的基本数据类型、引用类型,甚至是继承自父类的字段。

序列化的步骤

  1. 实现Serializable接口:你的类需要实现这个接口。
  2. **创建ObjectOutputStream**:用于将对象转换成字节流。
  3. 调用writeObject方法:将对象写入输出流。
  4. 关闭流:别忘了关闭流以释放资源。

反序列化的步骤大致相同,只不过是使用ObjectInputStreamreadObject方法。

4. 示例演示

让我们通过一个简单的例子来看看实际操作是怎样的。

定义一个可序列化的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.Serializable;

public class User implements Serializable {
private static final long serialVersionUID = 1L; // 推荐定义序列化版本号
private String name;
private int age;
private transient String password; // transient字段不会被序列化

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

// 省略getter和setter方法

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

序列化对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeDemo {
public static void main(String[] args) {
User user = new User("Alice", 30, "secret123");

try (FileOutputStream fileOut = new FileOutputStream("user.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {

out.writeObject(user);
System.out.println("对象已序列化到 user.ser 文件中.");
} catch (IOException i) {
i.printStackTrace();
}
}
}

运行上述代码后,你会发现当前目录下生成了一个名为user.ser的文件,这就是序列化后的字节流。

反序列化对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializeDemo {
public static void main(String[] args) {
User user = null;

try (FileInputStream fileIn = new FileInputStream("user.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {

user = (User) in.readObject();
System.out.println("反序列化后的对象: " + user);
} catch (IOException | ClassNotFoundException i) {
i.printStackTrace();
}
}
}

运行这段代码,你会看到输出:

1
反序列化后的对象: User{name='Alice', age=30, password='null'}

注意到password字段为空,这是因为它被声明为transient,在序列化过程中被忽略了。

5. 常见问题与注意事项

serialVersionUID是干嘛的?

serialVersionUID是序列化时用来验证版本兼容性的一个标识符。如果你不显式定义它,Java会根据类的结构自动生成。但为了避免类结构变化导致序列化失败,建议手动定义一个固定的值。

继承关系中的序列化

如果一个类的父类没有实现Serializable接口,那么在序列化子类对象时,父类的字段不会被序列化。反序列化时,父类的构造函数会被调用初始化父类部分。

处理敏感信息

使用transient关键字可以防止敏感信息被序列化,比如密码字段。此外,你也可以自定义序列化逻辑,通过实现writeObjectreadObject方法来更精细地控制序列化过程。

6. 总结

本文,我们深入浅出地探讨了Java中的序列化和反序列化,从基本概念到原理分析,再到实际的代码示例,希望你对这两个重要的技术点有了更清晰的理解。

为什么需要序列化和反序列化?

最直白的说,如果不进行持久化和网络传输,根本不需要序列化和反序列化。如果需要实现持久化和网络传输,就必须序列化和反序列化,因为对象是应用层的东西,不同的语言(比如:java,go,python)创建的对象还不一样,实现持久化和网络传输的载体根本不认这些对象。

7. 交流学习

最后,把猿哥的座右铭送给你:投资自己才是最大的财富。 如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。

drawing