当前位置:

首页 > 编程开发 > Java中对象的转移之序列化与反序列化方式

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

序列化将对象转换为字节流以便存储或传输,反序列化则将其还原。Java通过Serializable接口和ObjectOutputStream/ObjectInputStream实现该过程,transient关键字可保护敏感字段。原生序列化存在性能、跨语言及安全问题,因此常采用FastJson或Protobuf等替代方案,它们分别提供跨平台JSON处理与高效二进

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

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

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

序列化是什么?

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

反序列化是什么?

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

序列化的实现流程

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

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

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

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

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

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

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

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

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

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

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方法中,我们明确指定了将title和price字段以何种顺序、何种格式写入流中。
  • 在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来缓解,但在复杂的项目迭代中,维护版本兼容性依然是个头疼的问题。

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

替代方案一: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通信、大数据量持久化、多语言微服务集群等对效率和兼容性要求极高的场景。

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

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
作者最新文章
编程开发
相关文章 更多
Java之标准序列化机制详解
Java之标准序列化机制详解

Java标准序列化通过实现Serializable接口和ObjectOutputStream/ObjectInputStream实现对象持久化与网络传输,自动处理引用共享和循环引用。可通过transient关键字和自定义writeObject/readObject定制序列化,并利用serialVersionUID管理版本兼容。其优势在于使用简便,但存在格式私

Java对象序列化与反序列化教程
Java对象序列化与反序列化教程

Java对象序列化是将对象状态转换为可存储或传输形式的过程,反序列化则是将其恢复为对象。1.实现Serializable接口使类可序列化,且建议显式声明serialVersionUID以避免版本兼容性问题;2.使用ObjectOutputStream进行序列化操作,将对象写入文件;3.使用ObjectInputStream进行反序列化操作,从文件读取并还原对象;4.transient关键字用于标记不参与序列化的字段,反序列化后其值为默认值;5.可通过自定义writeObject()和readObject(

在分布式系统中,如何使用Java序列化?
在分布式系统中,如何使用Java序列化?

Java序列化在分布式系统中传输数据,通过java.io.Serializable接口实现。序列化对象需写入字节序列,通过ObjectOutputStream;反序列化需从字节序列重建对象,通过ObjectInputStream。在远程过程调用(如RMI)中,Java序列化可序列化方法参数和返回值。注意安全性、性能和版本控制等事项。

Java中对象的序列化的过程是什么?
Java中对象的序列化的过程是什么?

在Java中,对象序列化将对象转换为字节流,反序列化则将字节流还原为对象。序列化需要实现Serializable接口,准备对象并使用ObjectOutputStream写入数据;反序列化则使用ObjectInputStream读取数据并重建对象。例如,代码中序列化了一个具有name和age属性的Person对象,并从文件中反序列化以打印信息。

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

PDF教程
PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

Mac软件 更多
Postman macOS版
Postman macOS版
Mac

Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。

Docker Desktop macOS版
Docker Desktop macOS版
Mac

Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。

Android Studio macOS版
Android Studio macOS版
Mac

Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。

WINDOWS 更多
Postman Windows版
Postman Windows版
Windows

Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等

Docker Desktop Windows版
Docker Desktop Windows版
Windows

Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。

Android Studio Windows版
Android Studio Windows版
Windows

Android Studio 是 Google 面向 Android 应用开发提供的官方集成开发环境,以 IntelliJ IDEA 为基础,集成智能代码编辑、Gradle 构建、设备模拟、界面预览、调试测试及性能分析工具,