商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Java中对象的转移之序列化与反序列化方式

Java中对象的转移之序列化与反序列化方式

  发布于2026-05-20 阅读(0)

扫一扫,手机访问

1. 序列化与反序列化的定义与实现流程

在分布式系统、缓存以及跨应用数据交换的场景中,我们经常需要将对象“打包”发送或“封存”起来。这背后依赖的核心技术,就是序列化与反序列化。

Ja va中对象的转移之序列化与反序列化方式

序列化是什么?

简单来说,序列化就是把一个活生生的对象,转换成一串连续的字节流。这个过程就好比把一件复杂的家具拆解、打包成一个规整的包裹,目的是为了方便存储到文件或数据库,或者通过网络“寄送”到另一个系统。

反序列化是什么?

反序列化自然是序列化的逆过程。当系统收到这个“包裹”或者从存储介质中读取到这串字节流时,需要按照原来的图纸,把零件重新组装成完整的家具对象。这个过程就是反序列化,它让字节流重新获得生命,变回可操作的程序对象。

序列化的实现流程

整个流程可以清晰地分为三步走:

第一步:对象状态转换为字节流

  • 系统会提取对象的所有属性、数据结构以及类标识等信息,将它们编码成一组有序的字节。
  • 这组字节不仅包含了字段的值,还隐含了对象的“出生证明”(类信息),确保接收方能准确识别并还原。

第二步:传输或存储字节流

  • 生成的字节流可以被写入文件系统、存入数据库,或者直接通过网络套接字发送到远程服务器

第三步:将字节流转为对象(反序列化)

  • 当需要用到这个对象时,系统会读取字节流,根据其中包含的类信息和字段数据,在内存中重新构造出一个状态完全相同的对象实例。

正是这套机制,实现了对象跨越时间(持久化)和空间(网络传输)的转移。虽然这里以Ja va为例,但序列化的思想在众多编程语言和框架中都有体现,只是实现方式各有千秋。

2. Ja va中的序列化与反序列化实现

Ja va语言内置了一套完整的序列化机制,让对象转换变得非常直接。其核心是Serializable接口和一对流类:ObjectOutputStreamObjectInputStream

1.Serializable接口:标识对象可序列化

想让一个类的对象能被序列化,第一步就是让它实现Serializable接口。这个接口没有任何方法,是一个纯粹的“标记接口”,它的作用就是告诉JVM:“我这个类的对象可以被序列化。”

示例

import ja va.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;

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

    // 省略getters和setters
}

解释: 上面的Person类实现了Serializable接口。这意味着,这个类的所有实例字段(除非被特殊标记)在序列化时都会被自动转换成字节流。

2.ObjectOutputStream和ObjectInputStream:读写字节流的对象

  • ObjectOutputStream:负责将对象“写出去”,即执行序列化操作。
  • ObjectInputStream:负责将字节流“读进来”,即执行反序列化操作。

示例:序列化对象

import ja va.io.FileOutputStream;
import ja va.io.ObjectOutputStream;

public class SerializeExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            oos.writeObject(person); // 将Person对象写入文件
            System.out.println("对象已序列化");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

解释

  1. 代码创建了一个ObjectOutputStream,并将其链接到一个文件输出流。
  2. 调用writeObject方法,person对象的状态便被序列化并写入person.ser文件中。
  3. 至此,对象的数据已被持久化保存。

示例:反序列化对象

import ja va.io.FileInputStream;
import ja va.io.ObjectInputStream;

public class DeserializeExample {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person person = (Person) ois.readObject(); // 从文件中读取对象
            System.out.println("对象已反序列化: " + person.getName() + ", " + person.getAge());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

解释

  1. 代码创建ObjectInputStream来读取之前保存的文件。
  2. 调用readObject方法,字节流被重新解析,还原成一个Person对象。
  3. 最后,打印出对象的信息,验证反序列化成功。

3.transient关键字:保护敏感数据

序列化会包含所有非静态字段。但如果有些字段(比如密码、密钥)不希望被持久化或传输,该怎么办?这时就需要transient关键字出场了。用它修饰的字段,会在序列化过程中被自动忽略。

示例

public class User implements Serializable {
    private String username;
    private transient String password; // 不会被序列化

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

在这个例子中,即使用户对象被序列化,password字段也不会被写入字节流,从而在存储或传输时保护了敏感信息。

4.Externalizable接口:自定义序列化逻辑

对于更复杂的控制需求,Ja va提供了Externalizable接口。它继承自Serializable,但要求开发者自己动手,完全掌控序列化和反序列化的每一个细节。实现它需要重写两个方法:

  • writeExternal(ObjectOutput out):定义如何将对象写入字节流。
  • readExternal(ObjectInput in):定义如何从字节流中读取并重建对象。

示例

import ja va.io.Externalizable;
import ja va.io.ObjectOutput;
import ja va.io.ObjectInput;

public class Book implements Externalizable {
    private String title;
    private double price;

    public Book() {} // 必须有无参构造方法

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(title);
        out.writeDouble(price);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        title = in.readUTF();
        price = in.readDouble();
    }
}

解释

  • writeExternal方法中,我们明确指定了将titleprice字段以何种顺序、何种格式写入流中。
  • readExternal方法中,我们按照写入的顺序和格式,再将数据读取出来,赋值给对象的字段。
  • 这种方式牺牲了便利性,换来了极高的灵活性和控制力,适用于有特殊序列化需求的场景。

总的来说,Ja va内置的序列化机制为开发者提供了从便捷到灵活的不同选择。Serializable搭配ObjectOutputStream/ObjectInputStream足以应对大多数常规需求。而当需要对过程进行精细控制时,Externalizable接口则提供了强大的自定义能力。

3. Ja va序列化的缺点和替代方案

尽管Ja va原生序列化用起来方便,但在构建高性能、跨平台、高安全的现代分布式系统时,它的几个固有缺陷就会变得非常突出。

Ja va序列化的缺点

性能较差

  • Ja va序列化生成的字节流包含了大量元数据(如完整的类描述信息),导致数据体积臃肿。序列化和反序列化的过程也涉及大量反射操作,速度相对较慢。在处理海量数据或高并发请求时,这很容易成为性能瓶颈。

不跨语言

  • 它生成的是一种Ja va特有的二进制格式。这意味着,用Ja va序列化保存的数据,几乎无法被C++、Python、Go等其他主流语言直接解析。在微服务架构和多语言技术栈并存的今天,这无疑是一个巨大的限制。

存在安全漏洞

  • Ja va反序列化机制历史上存在严重的安全隐患。攻击者可以精心构造一个恶意的序列化字节流,当程序对其进行反序列化时,可能触发任意代码执行。这使得它不适合处理来自不可信源的数据。

缺乏版本控制

  • 当序列化后的类结构发生变化(例如增删字段),反序列化很容易失败。虽然可以通过手动维护serialVersionUID来缓解,但在复杂的项目迭代中,维护版本兼容性依然是个头疼的问题。

正因为这些局限性,在实际生产环境中,尤其是对性能、安全性和跨语言能力有要求的场景下,开发者往往会转向更优秀的替代方案。下面就来聊聊两个主流选择:FastJsonProtobuf

替代方案一:FastJson

FastJson是阿里巴巴开源的高性能JSON处理库。它的核心是将Ja va对象与JSON格式相互转换,而JSON是一种几乎无处不在的、人类可读的数据交换格式。

FastJson的优势

跨平台

  • JSON是标准的数据格式,所有编程语言都支持解析和生成。因此,用FastJson序列化的数据可以毫无障碍地在Ja va、前端Ja vaScript、Python后端等不同组件间传递。

速度快,性能优异

  • 正如其名,FastJson在速度上做了大量优化,其序列化/反序列化性能通常远超Ja va原生序列化,甚至优于许多其他JSON库。

支持灵活的数据结构

  • 基于JSON的键值对特性,FastJson能很好地处理动态或结构易变的数据。即使类的字段有增减,反序列化时也能通过字段名进行匹配,兼容性更好。

安全性更高

  • JSON格式本身不携带可执行代码,从根本上避免了Ja va原生反序列化漏洞。同时,FastJson也提供了安全模式,可以限制反序列化的类,进一步加固安全。

FastJson使用示例

import com.alibaba.fastjson.JSON;

public class FastJsonExample {
    public static void main(String[] args) {
        // 序列化
        Person person = new Person("Alice", 25);
        String jsonString = JSON.toJSONString(person);
        System.out.println("序列化为JSON字符串:" + jsonString);

        // 反序列化
        Person deserializedPerson = JSON.parseObject(jsonString, Person.class);
        System.out.println("反序列化对象:" + deserializedPerson.getName());
    }
}

替代方案二:Protobuf

Protobuf(Protocol Buffers)是Google推出的一种语言中立、平台无关、可扩展的序列化机制。它追求的是极致的效率和紧凑性。

Protobuf的优势

高效的二进制格式

  • Protobuf将数据编码成非常紧凑的二进制格式,体积远小于JSON或Ja va原生序列化结果。其编码和解码速度也极快,专为高性能网络通信设计。

跨语言

  • 通过预先定义.proto文件,Protobuf编译器可以生成Ja va、C++、Python、Go等多种语言的代码。不同语言编写的服务可以使用同一份数据定义进行无缝通信。

支持严格的版本控制

  • Protobuf的协议设计天生支持向前和向后兼容。新增或废弃字段不会破坏旧版程序的解析,大大简化了系统升级和数据迁移的复杂度。

安全性强

  • 由于其强类型和预定义结构的特性,Protobuf不易受到不可控反序列化攻击,安全性比Ja va原生序列化高得多。

Protobuf使用示例: 使用Protobuf首先需要定义一个.proto文件来描述数据结构:

// person.proto
syntax = "proto3";

message Person {
    string name = 1;
    int32 age = 2;
}

然后,使用Protobuf编译器生成对应的Ja va类,之后就可以像下面这样使用:

Person person = Person.newBuilder().setName("Alice").setAge(25).build();
byte[] data = person.toByteArray(); // 序列化
Person deserializedPerson = Person.parseFrom(data); // 反序列化

简单来说,FastJson以其通用性和可读性见长,非常适合前后端交互和需要日志调试的场景。而Protobuf则以绝对的性能和跨语言能力取胜,是微服务、分布式存储等对效率和带宽敏感场景的首选。两者都有效地弥补了Ja va原生序列化的短板。

4. 总结

序列化与反序列化是构建可交互、可扩展软件系统的基石技术。它们让对象得以跨越进程、网络甚至语言的边界,是实现数据持久化和远程通信的关键。

1. Ja va原生序列化的基础

Ja va通过Serializable接口和ObjectOutputStream/ObjectInputStream这一套组合拳,提供了开箱即用的序列化支持。其流程直观:对象变字节流,存储或传输;字节流再变回对象,恢复状态。这套方案在单一的Ja va环境中简单快捷。

2. Ja va原生序列化的缺点

然而,一旦进入生产环境,面对性能、安全、跨语言的需求,原生序列化的局限性便暴露无遗:

  • 性能低:生成的负载大,处理速度慢。
  • 不跨语言:二进制格式与其他语言不兼容。
  • 安全性欠佳:存在已知的反序列化安全漏洞。

因此,它更适用于简单的、临时的、信任边界内的数据存储场景。

3. 替代方案:FastJson和Protobuf

为了应对更复杂的挑战,业界广泛采用了更先进的序列化方案:

  • FastJson:在需要人类可读、跨平台(尤其是Web前后端)且对性能有一定要求的场景下表现出色。JSON格式的通用性是其最大优势。
  • Protobuf:在追求极致性能、低带宽消耗和严格跨语言兼容的分布式系统、微服务架构中几乎是标准选择。其二进制格式高效且安全。

使用场景总结

  • 选择Ja va原生序列化:适用于简单的Ja va进程内缓存、Session存储等封闭场景。
  • 选择FastJson:适用于前后端API交互、配置文件存储、需要人工查看和调试数据的场景。
  • 选择Protobuf:适用于服务间RPC通信、大数据量持久化、多语言微服务集群等对效率和兼容性要求极高的场景。

技术选型没有银弹,关键在于理解不同序列化工具的特性,并根据实际应用场景在性能、可读性、安全性和开发成本之间做出平衡。选择合适的序列化方案,能显著提升系统的整体健壮性和效率。

本文转载于:https://www.jb51.net/program/364067m9z.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注