目录
序列化与反序列化
为什么需要序列化
常见的序列化方式
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 格式,具有架构演化的能力,适用于大数据处理和数据仓库。
首先,假设我们有一个名为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 @Override public 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 变量的值被设为初始值,如 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手动序列化transientField private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // 调用默认的序列化方法 // 手动序列化transientField out.writeObject(transientField); } // 使用readObject手动反序列化transientField private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 调用默认的反序列化方法 // 手动反序列化transientField transientField = (String) in.readObject(); } @Override public 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 ArrayList instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the ArrayList * instance is emitted (int), followed by all of its elements * (each an Object) in the proper order. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int 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; iJava序列化的简单总结
Java序列化只是针对对象的状态进行保存,至于对象中的方法,序列化不关心
当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口
当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进 行序列化(实现深度克隆)
当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段
被申明为transient的字段,如果需要序列化,可以添加两个私有方法:writeObject 和 readObject