![]()
序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化,序列化与反序列化的根本目的是数据的传输
# 序列化以反序列化
Java 序列化是指把 Java 对象转换为字节序列的过程,将数据分解成字节流以便存储在文件中或在网络上传输;而 Java 反序列化是指把字节序列恢复为 Java 对象的过程,打开字节流并重构对象
1 2
| 序列化:对象 -> 字符串(字节流) 反序列化:字符串(字节流) -> 对象
|
比如:现在我们都会在淘宝上买桌子,桌子这种很不规则的东西,该怎么从一个城市运输到另一个城市,这时候一般都会把它拆掉成板子,再装到箱子里面,就可以快递寄出去了,这个过程就类似我们的序列化的过程(把数据转化为可以存储或者传输的形式)。当买家收到货后,就需要自己把这些板子组装成桌子的样子,这个过程就像反序列的过程(转化成当初的数据对象)
# ObjectOutputStream
序列化
1 2 3 4 5
| ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
|
1 2 3 4 5 6
| ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj;
|
# 为什么需要序列化
网站中两个进程进行远程通信,互相发送各种类型的数据,图片 视频 文档,这些数据会以二进制序列形式进行传递,使用 Java
序列化和反序列化实现进程间对象传送
- 发送方将发送的数据对象转换为
Java
字节序列,拆分为字节流 序列化过程 - 接收方将收到的数据从字节流重构转化为当初的数据读性对象
# Serializable
接口序列化
# 接口 Serializable
序列化接口 Serializable
接口是一个标识接口,它的主要作用就是标识这个对象是可序列化,
接口应该使用 Java 标准库中的 java.io.Serializable
而不是自定义的 a.Serializable
接口
有实现了 Serializable
或者 Externalizable
接口的类的对象才能被序列化为字节序列。(不是则会抛出异常;实现 Serializable
接口,类表明它是可序列化的,可以被 ObjectOutputStream
序列化,以及被 ObjectInputStream
反序列化
1 2 3 4 5 6
|
public interface Serializable {
}
|
![]()
# 实现代码
person.java 实现对象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import java.io.Serializable;
public class person implements Serializable {
private String name; private int age;
public person(){
} public person(String name, int age){ this.name = name; this.age = age; }
@Override public String toString(){ return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }
}
|
SerializationTest.java 序列化文件类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutput; import java.io.ObjectOutputStream;
public class SerializationTest {
public static void serialize(Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static void main(String[] args) throws Exception{ person person = new person("aa",22); System.out.println(person); serialize(person); } }
-----------------------------------------------
输出:
Person{name='aa', age=22}
|
UnserializeTest.java 反序列化文件类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream;
public class UnserializeTest { public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }
public static void main(String[] args) throws Exception{ person person = (person)unserialize("ser.bin");
System.out.println(person); } }
-----------------------------------------------
输出:
Person{name='aa', age=22}
|
# 序列化注意点
反序列化过程中,它的父类如果没有实现序列化接口,那么将需要提供无参构造函数来重新创建对象
序列化的对象是已经继承了其他类变成了子类的化,如果父类没有实现接口 Serializable
, 那么父类要么选择实现这个接口,要么是提供无参构造器,在反序列化 Child
对象时, Java
会调用 Parent
类的无参构造函数来重新创建 Parent
对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.io.Serializable;
public class Parent { private int id; public Parent() { } public Parent(int id) { this.id = id; } }
public class Child extends Parent implements Serializable { private String name; public Child(String name, int id) { super(id); this.name = name; } }
|
实现 Serializable
接口的子类也是可以被序列化的
当一个父类类实现了 Serializable
接口,它的子类也会自动被序列化。这意味着,如果一个父类实现了 Serializable
接口,那么它的所有子类都可以被序列化,无需再单独实现 Serializable
接口;即子类也被视为可序列化的。这样可以确保整个继承层次结构在序列化和反序列化过程中的一致性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.io.Serializable;
public class Parent implements Serializable { private int id; public Parent(int id) { this.id = id; } }
public class Child extends Parent { private String name; public Child(String name, int id) { super(id); this.name = name; } }
|
静态成员变量是不能被序列化 序列化是针对对象属性的,而静态成员变量是属于类的
静态成员变量属于类级别而不是实例级别,它们不会被包含在序列化的过程中。当对象被序列化时,只有实例变量会被保存到序列化的数据流中,静态成员变量不会被包含在序列化的数据中,。如果需要在序列化过程中保存静态成员变量的状态,可以通过自定义序列化和反序列化方法来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import java.io.Serializable;
public class MyClass implements Serializable { private static int staticVar = 10; private int instanceVar; public MyClass(int instanceVar) { this.instanceVar = instanceVar; } public static void main(String[] args) { MyClass obj = new MyClass(20); } }
|
transient
标识的对象成员变量不参与序列化
一个对象的成员变量被标记为 transient
时,在对象进行序列化时,这些被标记的成员变量将不会被序列化,即它们的值不会被保存到序列化的数据流中。只会参与这个过程并输出对应数据类型的默认值, int
类型输出 ** 0
** , 但原始对象中的值并没有改变,仍然是 ** 30
**,只要添加了 transient
,序列化运行时会跳过该字段的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| package a;
import java.io.*;
class Person implements Serializable { private String name; private transient int age;
public Person(String name, int age) { this.name = name; this.age = age; }
@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
public class SerializationExample { public static void main(String[] args) { Person person = new Person("Alice", 30);
try { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser")); out.writeObject(person); out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser")); Person deserializedPerson = (Person) in.readObject(); in.close();
System.out.println(deserializedPerson); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
--------------------------------------
Person{name='Alice', age=0}
|
# Externalizable
接口序列化
参考文章:
Java 序列化之 Externalizable - 腾讯云开发者社区 - 腾讯云 (tencent.com)
# 接口 Externalizable
JDK 中除了提供 Serializable
序列化接口外,还提供了另一个序列化接口 Externalizable
,使用该接口之后,之前基于 Serializable
接口的序列化机制就将失效。 Externalizable
的序列化机制优先级要高于 Serializable
源码中 Externalizable
接口继承了 Serializable
接口,并定义了两个方法,实现此接口后,类中属性字段使用 transient
和不使用没有任何区别
1 2
| writeExterna(写入 userName 和 password 两个数据) readExternal(读取反序列化的信息并赋值给 userName 和 password 两个字段)
|
![]()
# 实现代码
User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package a;
import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput;
public class User implements Externalizable {
private static final long serialVersionUID = 1318824539146791009L; private String userName; private transient String password;
public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User [userName=" + userName + ", password=" + password + "]"; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(this.userName); out.writeObject(this.password); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.userName = in.readObject().toString(); this.password = in.readObject().toString(); } }
|
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import java.io.*;
public class Test{ public static void main(String[] args) throws Exception {
File file = new File("d:\\a.user"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); User user1 = new User(); user1.setUserName("zhangsan"); user1.setPassword("123456"); oos.writeObject(user1);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); User user2 = (User) ois.readObject(); System.out.println(user2); } }
-----------------------------------------
输出:
User [userName=zhangsan, password=123456]
|
# 序列化注意点
Externalizable
进行反序列化时,需要有默认的构造方法,有参的构造器是不够的,还需要自己手写一个默认无参的构造器,因为使用 Externalizable
进行反序列化时,需要有默认的构造方法,通过反射先创建出该类的实例,然后再把解析后的属性值,通过反射赋值。
Serializable
接口则不需要单独写默认无参的构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class User implements Externalizable {
private static final long serialVersionUID = 1318824539146791009L; private String userName; private transient String password;
public User(){
}
public User(String userName, String password) { super(); this.userName = userName; this.password = password; } }
|
User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package a;
import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput;
public class User implements Externalizable {
private static final long serialVersionUID = 1318824539146791009L; private String userName; private transient String password;
public User(){
}
public User(String userName, String password) { super(); this.userName = userName; this.password = password; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User [userName=" + userName + ", password=" + password + "]"; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(this.userName); out.writeObject("externalizable:"+this.password); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.userName = (String)in.readObject(); this.password = (String)in.readObject(); } }
|
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package a;
import java.io.*;
public class Test { public static void main(String[] args) throws Exception { File file = new File("d:\\a.user"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); User user1 = new User("zhangsan", "123456"); oos.writeObject(user1); System.out.println(oos);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream tempOos = new ObjectOutputStream(bos); tempOos.writeObject(user1); tempOos.close();
System.out.println("-------序列化成功");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); User user2 = (User) ois.readObject(); System.out.println(user2); System.out.println("-------反序列化成功");
byte[] bytes = bos.toByteArray(); String serializedData = new String(bytes, "UTF-8"); System.out.println(serializedData); } }
--------------------------------
输出:
java.io.ObjectOutputStream@20ad9418 -------序列化成功 User [userName=zhangsan, password=externalizable:123456] -------反序列化成功 �� sr a.UserMg�P�a xptzhangsant externalizable:123456x
|
# 序列化安全
序列化安全主要是由于 writeObject()
readObject()
这两种方法是可以通过开发者重写的,重写的场景一般是这样的
假设有一个名为 MyList
的类,其中定义了一个名为 arr
的数组属性,初始数组长度为 100
。在实际序列化过程中,如果让 arr 属性参与序列化,那么整个长度为 100
的数组都会被序列化,,但是数组中存放的数据只有 50 个数据,,如果整个数组全部序列化的数据量过大且浪费空间,所以需要重写方法自定义序列化过程
在上文代码的基础上加入重写的 readObject
反序列化方法,然后按照顺序运行序列化程序 SerializationTest.java
再运行反序列化程序 UnserializeTest.java
, 会执行重写的方法并且执行外部命令弹出计算机 calc
, 这是最理想的情况,但是此情况几乎不会出现
- 入口参数中包含可控类,该类有危险方法,
readObject
时调用 - 入口类参数中包含可控类,该类又调用其他有危险方法的类,
readObject
时调用 - 构造函数 / 静态代码块等类加载时隐式执行
person.java 实现对象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable;
public class person implements Serializable {
private String name; private int age;
public person(){
} public person(String name, int age){ this.name = name; this.age = age; }
@Override public String toString(){ return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); Runtime.getRuntime().exec("calc"); } }
|
![]()