目录
序列化与反序列化
为什么需要序列化
常见的序列化方式
java的序列化示例
transient排除序列化
Java序列化的简单总结
序列化与反序列化
-
序列化就是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化。
-
反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程称为对象的反序列化。
为什么需要序列化
-
数据持久化:序列化使得对象可以被保存到磁盘或数据库中,以便在程序重新启动时恢复状态。这在许多应用中很有用,如保存应用设置、用户数据、游戏进度等。
-
跨平台通信:不同的计算机和编程语言可能使用不同的数据表示形式。序列化可以将数据转换为通用的格式(如JSON或XML),从而实现跨平台和跨语言的通信,使不同系统之间能够互相交换数据。
-
远程调用和分布式系统:在分布式系统中,远程服务器上的方法可能需要接收和返回对象。序列化可以将对象编码为字节流,以便在网络上传输,然后在另一端进行反序列化。这使得远程过程调用(RPC)和分布式系统更容易实现。
-
消息传递:在消息传递系统中,消息通常以序列化的形式进行传输。这在消息队列、微服务架构和事件驱动系统中非常常见。
-
缓存:序列化可以用于将对象缓存到内存中,以加快访问速度。缓存可以存储序列化的数据,而不必在每次访问时重新创建对象。
-
复制和克隆:通过序列化,可以轻松地创建对象的副本或克隆,而不必手动复制每个字段。
-
数据传输和备份:在数据传输和备份过程中,序列化可以帮助将数据从一个位置传输到另一个位置,或创建数据的备份。
常见的序列化方式
随着分布式架构、微服务架构的普及。服务与服务之间的通信成了最基本的需求。而对于序列化的要求也逐渐迫切,衍生出了一系列具有高效数据传输性能的热点技术。下面列举了一些常见的序列化方式:
-
Java 序列化:
-
Java 提供了内置的序列化机制,通过实现
java.io.Serializable
接口可以使一个 Java 对象可序列化。可以使用ObjectOutputStream
将对象序列化为字节流,然后使用ObjectInputStream
进行反序列化。这种方式是 Java 特有的,不适用于与其他编程语言通信。
-
-
JSON(JavaScript Object Notation)序列化:
-
JSON 是一种文本格式,用于表示结构化数据。它支持序列化和反序列化对象数据,并且是跨语言通信的常见方式。许多编程语言都有用于处理 JSON 数据的库。例如,在 Java 中,可以使用 Gson、Jackson 等库将对象转换为 JSON 字符串,然后再将 JSON 字符串还原为对象。
-
-
XML(eXtensible Markup Language)序列化:
-
XML 是一种标记语言,常用于表示和交换数据。类似于 JSON,XML 也可以用于序列化和反序列化对象数据。在 Java 中,可以使用 JAXB(Java Architecture for XML Binding)库来实现 XML 序列化和反序列化。
-
-
Protocol Buffers(protobuf)序列化:
-
Protocol Buffers 是一种二进制格式,用于高效地序列化和反序列化数据。它支持多种编程语言,并且具有较小的数据大小和较高的性能。Google 的 Protocol Buffers 是最知名的实现之一。
-
-
MessagePack 序列化:
-
MessagePack 是一种二进制序列化格式,类似于 JSON,但更轻量和高效。它支持多种编程语言,并且适用于高性能应用程序。
-
-
Thrift 序列化:
-
Apache Thrift 是一种跨语言的序列化框架,支持多种数据传输格式,包括二进制、JSON 和 XML。它用于构建高性能、跨语言的分布式系统。
-
-
Avro 序列化:
-
Apache Avro 是一种数据序列化系统,设计用于大规模数据处理。它支持 JSON 格式,具有架构演化的能力,适用于大数据处理和数据仓库。
-
java的序列化示例
首先,假设我们有一个名为Person
的Java类,我们想要将其序列化和反序列化:
public class Person implements Serializable {private String name;private int age; public Person(String name, int age) {this.name = name;this.age = age;} // Getter and Setter methods @Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + '}';} }
接下来,我们将演示如何将Person
对象序列化为字节流,并从字节流中反序列化回Person
对象:
public class SerializationExample {public static void main(String[] args) {// 创建一个Person对象Person person = new Person("Alice", 30); try {// 序列化到文件FileOutputStream fileOutputStream = new FileOutputStream("person.ser");ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(person);objectOutputStream.close();fileOutputStream.close();System.out.println("Person对象已经被序列化到person.ser文件"); // 从文件中反序列化回Person对象FileInputStream fileInputStream = new FileInputStream("person.ser");ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);Person deserializedPerson = (Person) objectInputStream.readObject();objectInputStream.close();fileInputStream.close(); System.out.println("从person.ser文件中反序列化的Person对象:");System.out.println(deserializedPerson); } catch (IOException | ClassNotFoundException e) {e.printStackTrace();}} }
上述示例中,我们首先创建了一个Person
对象,然后使用ObjectOutputStream
将其序列化为名为person.ser
的文件。接着,我们使用ObjectInputStream
从该文件中读取数据,并将其反序列化为一个新的Person
对象。
需要注意的是,要使类可序列化,需要实现Serializable
接口,这是Java序列化机制的要求。此外,对象的字段也必须是可序列化的,或者将它们标记为transient
以排除序列化。
transient排除序列化
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
然而,有时候您可能需要绕过transient
机制,将某些transient
字段也包含在对象的序列化表示中,这可以使用自定义的writeObject
和readObject
方法来实现。这样可以实现对transient
字段的手动序列化和反序列化控制。
class MyClass implements Serializable {private transient String transientField = "I'm transient";private String nonTransientField = "I'm not transient"; // 使用writeObject手动序列化transientFieldprivate void writeObject(ObjectOutputStream out) throws IOException {out.defaultWriteObject(); // 调用默认的序列化方法 // 手动序列化transientFieldout.writeObject(transientField);} // 使用readObject手动反序列化transientFieldprivate void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject(); // 调用默认的反序列化方法 // 手动反序列化transientFieldtransientField = (String) in.readObject();} @Overridepublic String toString() {return "transientField: " + transientField + ", nonTransientField: " + nonTransientField;} } public class Main {public static void main(String[] args) throws IOException, ClassNotFoundException {MyClass obj = new MyClass(); // 将对象序列化到文件FileOutputStream fileOut = new FileOutputStream("object.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(obj);out.close();fileOut.close(); // 从文件中反序列化对象FileInputStream fileIn = new FileInputStream("object.ser");ObjectInputStream in = new ObjectInputStream(fileIn);MyClass deserializedObj = (MyClass) in.readObject();in.close();fileIn.close(); System.out.println(deserializedObj); // 输出序列化后的对象,transientField不再是null} }
在上面的示例中,transientField
被标记为transient
,但我们使用自定义的writeObject
和readObject
方法手动序列化和反序列化了它。这使得transientField
可以包含在序列化表示中,并且在反序列化后保留其值。请注意,在自定义的writeObject
方法中,我们首先调用out.defaultWriteObject()
以调用默认的序列化方法,然后手动序列化transientField
。在自定义的readObject
方法中,我们首先调用in.defaultReadObject()
以调用默认的反序列化方法,然后手动反序列化transientField
。
ArrayList中的Object[]就是transient修饰的,利用writeObject绕过transient机制,目的是为了更高效地尽可能精简地返回数组数据。
/*** The array buffer into which the elements of the ArrayList are stored.* The capacity of the ArrayList is the length of this array buffer. Any* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA* will be expanded to DEFAULT_CAPACITY when the first element is added.*/transient Object[] elementData; // non-private to simplify nested class access /*** Save the state of the <tt>ArrayList</tt> instance to a stream (that* is, serialize it).** @serialData The length of the array backing the <tt>ArrayList</tt>* instance is emitted (int), followed by all of its elements* (each an <tt>Object</tt>) in the proper order.*/private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{// Write out element count, and any hidden stuffint expectedModCount = modCount;s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone()s.writeInt(size); // Write out all elements in the proper order.for (int i=0; i<size; i++) {s.writeObject(elementData[i]);} if (modCount != expectedModCount) {throw new ConcurrentModificationException();}}
Java序列化的简单总结
-
Java序列化只是针对对象的状态进行保存,至于对象中的方法,序列化不关心
-
当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口
-
当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进 行序列化(实现深度克隆)
-
当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段
-
被申明为transient的字段,如果需要序列化,可以添加两个私有方法:writeObject 和 readObject