聊聊分布式架构03——序列化和反序列化

目录

序列化与反序列化

为什么需要序列化

常见的序列化方式

java的序列化示例

transient排除序列化

Java序列化的简单总结


序列化与反序列化
  • 序列化就是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化。

  • 反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程称为对象的反序列化。

为什么需要序列化
  1. 数据持久化:序列化使得对象可以被保存到磁盘或数据库中,以便在程序重新启动时恢复状态。这在许多应用中很有用,如保存应用设置、用户数据、游戏进度等。

  2. 跨平台通信:不同的计算机和编程语言可能使用不同的数据表示形式。序列化可以将数据转换为通用的格式(如JSON或XML),从而实现跨平台和跨语言的通信,使不同系统之间能够互相交换数据。

  3. 远程调用和分布式系统:在分布式系统中,远程服务器上的方法可能需要接收和返回对象。序列化可以将对象编码为字节流,以便在网络上传输,然后在另一端进行反序列化。这使得远程过程调用(RPC)和分布式系统更容易实现。

  4. 消息传递:在消息传递系统中,消息通常以序列化的形式进行传输。这在消息队列、微服务架构和事件驱动系统中非常常见。

  5. 缓存:序列化可以用于将对象缓存到内存中,以加快访问速度。缓存可以存储序列化的数据,而不必在每次访问时重新创建对象。

  6. 复制和克隆:通过序列化,可以轻松地创建对象的副本或克隆,而不必手动复制每个字段。

  7. 数据传输和备份:在数据传输和备份过程中,序列化可以帮助将数据从一个位置传输到另一个位置,或创建数据的备份。

常见的序列化方式

随着分布式架构、微服务架构的普及。服务与服务之间的通信成了最基本的需求。而对于序列化的要求也逐渐迫切,衍生出了一系列具有高效数据传输性能的热点技术。下面列举了一些常见的序列化方式:

  1. Java 序列化

    • Java 提供了内置的序列化机制,通过实现 java.io.Serializable 接口可以使一个 Java 对象可序列化。可以使用 ObjectOutputStream 将对象序列化为字节流,然后使用 ObjectInputStream 进行反序列化。这种方式是 Java 特有的,不适用于与其他编程语言通信。

  2. JSON(JavaScript Object Notation)序列化

    • JSON 是一种文本格式,用于表示结构化数据。它支持序列化和反序列化对象数据,并且是跨语言通信的常见方式。许多编程语言都有用于处理 JSON 数据的库。例如,在 Java 中,可以使用 Gson、Jackson 等库将对象转换为 JSON 字符串,然后再将 JSON 字符串还原为对象。

  3. XML(eXtensible Markup Language)序列化

    • XML 是一种标记语言,常用于表示和交换数据。类似于 JSON,XML 也可以用于序列化和反序列化对象数据。在 Java 中,可以使用 JAXB(Java Architecture for XML Binding)库来实现 XML 序列化和反序列化。

  4. Protocol Buffers(protobuf)序列化

    • Protocol Buffers 是一种二进制格式,用于高效地序列化和反序列化数据。它支持多种编程语言,并且具有较小的数据大小和较高的性能。Google 的 Protocol Buffers 是最知名的实现之一。

  5. MessagePack 序列化

    • MessagePack 是一种二进制序列化格式,类似于 JSON,但更轻量和高效。它支持多种编程语言,并且适用于高性能应用程序。

  6. Thrift 序列化

    • Apache Thrift 是一种跨语言的序列化框架,支持多种数据传输格式,包括二进制、JSON 和 XML。它用于构建高性能、跨语言的分布式系统。

  7. 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
​
    @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 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

然而,有时候您可能需要绕过transient机制,将某些transient字段也包含在对象的序列化表示中,这可以使用自定义的writeObjectreadObject方法来实现。这样可以实现对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,但我们使用自定义的writeObjectreadObject方法手动序列化和反序列化了它。这使得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 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; i<size; i++) {
            s.writeObject(elementData[i]);
        }
​
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
Java序列化的简单总结
  1. Java序列化只是针对对象的状态进行保存,至于对象中的方法,序列化不关心

  2. 当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口

  3. 当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进 行序列化(实现深度克隆)

  4. 当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段

  5. 被申明为transient的字段,如果需要序列化,可以添加两个私有方法:writeObject 和 readObject

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Elaine202391

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值