Java——标准序列化机制

深度详解Java序列化  对象序列化是将对象转换为字节序列的过程,使其可以在网络上进行传输或者保存到磁盘上。本文将详细介绍Java对象序列化的实现机制、工作原理和注意事项。希望能对学习Java的同学们有所帮助。 文章目录 1. Java对象序列化的作用 2. Java对象序列化实现机制 3. 序列化的注意事项 4. 常用的序列化协议 4.1 Java标准序列化 4.2 JSON 4.3 Protobuf 4.4 Thrift 5. 总结 阅读详情

1、基本用法

要让一个类支持序列化,只需要让这个类实现接口java.io.Serializable。Serializable没有定义任何方法,只是一个标记接口。比如,对于前面章节提到的Student类,为支持序列化,可改为:

public class Student implements Serializable {
    //省略主体代码
}

声明实现了Serializable接口后,保存/读取Student对象就可以使用ObjectOutput-Stream/ObjectInputStream流了。

ObjectOutputStream是OutputStream的子类,但实现了Object-Output接口。ObjectOutput是DataOutput的子接口,增加了一个方法:

public void writeObject(Object obj) throws IOException

这个方法能够将对象obj转化为字节,写到流中。

ObjectInputStream是InputStream的子类,它实现了ObjectInput接口。ObjectInput是DataInput的子接口,增加了一个方法:

public Object readObject() throws ClassNotFoundException, IOException

这个方法能够从流中读取字节,转化为一个对象。

使用这两个流,保存学生列表的代码就可以变为:

public static void writeStudents(List<Student> students)
    throws IOException {
    ObjectOutputStream out = new ObjectOutputStream(
            new BufferedOutputStream(new FileOutputStream("students.dat")));
    try {
        out.writeInt(students.size());
        for(Student s : students) {
            out.writeObject(s);
        }
    } finally {
        out.close();
    }
}

而从文件中读入学生列表的代码可以变为:

public static List<Student> readStudents() throws IOException,
        ClassNotFoundException {
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(
            new FileInputStream("students.dat")));
    try {
        int size = in.readInt();
        List<Student> list = new ArrayList<>(size);
        for(int i = 0; i < size; i++) {
            list.add((Student) in.readObject());
        }
        return list;
    } finally {
        in.close();
    }
}

实际上,只要List对象也实现了Serializable(ArrayList/LinkedList都实现了)​,上面代码还可以进一步简化,读写只需要一行代码,如下所示:

public static void writeStudents(List<Student> students)
    throws IOException {
    ObjectOutputStream out = new ObjectOutputStream(
            new BufferedOutputStream(new FileOutputStream("students.dat")));
    try {
        out.writeObject(students);
    } finally {
        out.close();
    }
}
public static List<Student> readStudents() throws IOException,
        ClassNotFoundException {
    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(
            new FileInputStream("students.dat")));
    try {
        return (List<Student>) in.readObject();
    } finally {
        in.close();
    }
}

是不是很神奇?只要将类声明实现Serializable接口,然后就可以使用ObjectOutput-Stream/ObjectInputStream直接读写对象了。我们之前介绍的各种类,如String、Date、Double、ArrayList、LinkedList、HashMap、TreeMap等,都实现了Serializable。

2、复杂对象

上面例子中的Student对象是非常简单的,如果对象比较复杂呢?比如:

  1. 如果a、b两个对象都引用同一个对象c,序列化后c是保存两份还是一份?在反序列化后还能让a、b指向同一个对象吗?答案是,c只会保存一份,反序列化后指向相同对象。
  2. 如果a、b两个对象有循环引用呢?即a引用了b,而b也引用了a。这种情况Java也没问题,可以保持引用关系。

这就是Java序列化机制的神奇之处,它能自动处理引用同一个对象的情况,也能自动处理循环引用的情况,具体例子我们就不介绍了。

3、定制序列化

默认的序列化机制已经很强大了,它可以自动将对象中的所有字段自动保存和恢复,但这种默认行为有时候不是我们想要的。

对于有些字段,它的值可能与内存位置有关,比如默认的hashCode()方法的返回值,当恢复对象后,内存位置肯定变了,基于原内存位置的值也就没有了意义。还有一些字段,可能与当前时间有关,比如表示对象创建时的时间,保存和恢复这个字段就是不正确的。

还有一些情况,如果类中的字段表示的是类的实现细节,而非逻辑信息,那默认序列化也是不适合的。为什么不适合呢?因为序列化格式表示一种契约,应该描述类的逻辑结构,而非与实现细节相绑定,绑定实现细节将使得难以修改,破坏封装。

比如,我们在容器类中介绍的LinkedList,它的默认序列化就是不适合的。为什么呢?因为LinkedList表示一个List,它的逻辑信息是列表的长度,以及列表中的每个对象,但LinkedList类中的字段表示的是链表的实现细节,如头尾节点指针,对每个节点,还有前驱和后继节点指针等。

那怎么办呢?Java提供了多种定制序列化的机制,主要的有两种:

  • 一种是transient关键字
  • 另外一种是实现writeObject和readObject方法。

将字段声明为transient,默认序列化机制将忽略该字段,不会进行保存和恢复。比如,类LinkedList中,它的字段都声明为了transient,如下所示:

transient int size = 0;
transient Node<E> first;
transient Node<E> last;

声明为了transient,不是说就不保存该字段了,而是告诉Java默认序列化机制,不要自动保存该字段了,可以实现writeObject/readObject方法来自己保存该字段。

类可以实现writeObject方法,以自定义该类对象的序列化过程,其声明必须为:

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException

可以在这个方法中,调用ObjectOutputStream的方法向流中写入对象的数据。比如, LinkedList使用如下代码序列化列表的逻辑数据:

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    s.defaultWriteObject();
    //写元素个数
    s.writeInt(size);
    //循环写每个元素
    for(Node<E> x = first; x ! = null; x = x.next)
        s.writeObject(x.item);
}

需要注意的是代码:

s.defaultWriteObject();

这一行是必需的,它会调用默认的序列化机制,默认机制会保存所有没声明为transient的字段,即使类中的所有字段都是transient,也应该写这一行,因为Java的序列化机制不仅会保存纯粹的数据信息,还会保存一些元数据描述等隐藏信息,这些隐藏的信息是序列化之所以能够神奇的重要原因。

与writeObject对应的是readObject方法,通过它自定义反序列化过程,其声明必须为:

private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException

在这个方法中,调用ObjectInputStream的方法从流中读入数据,然后初始化类中的成员变量。比如,LinkedList的反序列化代码为:

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();
    //读元素个数
    int size = s.readInt();
    //循环读入每个元素
    for (int i = 0; i < size; i++)
        linkLast((E)s.readObject());
}

注意代码:

s.defaultReadObject();

这一行代码也是必需的。

除了自定义writeObject/readObject方法,还有一些自定义序列化过程的机制:Exter-nalizable接口、readResolve方法和writeReplace方法,这些机制用得相对较少,我们就不介绍了。

4、序列化的基本原理

  1. 如果类的字段表示的就是类的逻辑信息,如上面的Student类,那就可以使用默认序列化机制,只要声明实现Serializable接口即可。
  2. 否则的话,如LinkedList,那就可以使用transient关键字,实现writeObject和read-Object自定义序列化过程。
  3. Java的序列化机制可以自动处理如引用同一个对象、循环引用等情况。

序列化到底是如何发生的呢?关键在ObjectOutputStream的writeObject和ObjectInput-Stream的readObject方法内。它们的实现都非常复杂,正因为这些复杂的实现才使得序列化看上去很神奇,我们简单介绍其基本逻辑。

writeObject的基本逻辑是:

  1. 如果对象没有实现Serializable,抛出异常NotSerializableException。
  2. 每个对象都有一个编号,如果之前已经写过该对象了,则本次只会写该对象的引用,这可以解决对象引用和循环引用的问题。
  3. 如果对象实现了writeObject方法,调用它的自定义方法。
  4. 默认是利用反射机制,遍历对象结构图,对每个没有标记为transient的字段,根据其类型,分别进行处理,写出到流,流中的信息包括字段的类型,即完整类名、字段名、字段值等。

readObject的基本逻辑是:

  • 不调用任何构造方法;
  • 它自己就相当于是一个独立的构造方法,根据字节流初始化对象,利用的也是反射机制;
  • 在解析字节流时,对于引用到的类型信息,会动态加载,如果找不到类,会抛出ClassNotFoundException。

5、版本问题

代码是在不断演化的,而序列化的对象可能是持久保存在文件上的,如果类的定义发生了变化,那持久化的对象还能反序列化吗?

默认情况下,Java会给类定义一个版本号,这个版本号是根据类中一系列的信息自动生成的。在反序列化时,如果类的定义发生了变化,版本号就会变化,与流中的版本号就会不匹配,反序列化就会抛出异常,类型为java.io.InvalidClassException。

通常情况下,我们希望自定义这个版本号,而非让Java自动生成,一方面是为了更好地控制,另一方面是为了性能,因为Java自动生成的性能比较低。怎么自定义呢?在类中定义如下变量:

private static final long serialVersionUID = 1L;

在Java IDE如Eclipse中,如果声明实现了Serializable而没有定义该变量,IDE会提示自动生成。这个变量的值可以是任意的,代表该类的版本号。在序列化时,会将该值写入流,在反序列化时,会将流中的值与类定义中的值进行比较,如果不匹配,会抛出InvalidClassException。

那如果版本号一样,但实际的字段不匹配呢?Java会分情况自动进行处理,以尽量保持兼容性,大概分为三种情况:

  • 字段删掉了:即流中有该字段,而类定义中没有,该字段会被忽略;
  • 新增了字段:即类定义中有,而流中没有,该字段会被设为默认值;
  • 字段类型变了:对于同名的字段,类型变了,会抛出InvalidClassException。

6、序列化特点分析

序列化的主要用途有两个:一个是对象持久化;另一个是跨网络的数据交换、远程过程调用。Java标准的序列化机制有很多优点,使用简单,可自动处理对象引用和循环引用,也可以方便地进行定制,处理版本问题等,但它也有一些重要的局限性。

  1. Java序列化格式是一种私有格式,是一种Java特有的技术,不能被其他语言识别,不能实现跨语言的数据交换。
  2. Java在序列化字节中保存了很多描述信息,使得序列化格式比较大。
  3. Java的默认序列化使用反射分析遍历对象结构,性能比较低。
  4. Java的序列化格式是二进制的,不方便查看和修改。

由于这些局限性,实践中往往会使用一些替代方案。在跨语言的数据交换格式中,ⅩML/JSON是被广泛采用的文本格式,各种语言都有对它们的支持,文件格式清晰易读。有很多查看和编辑工具,它们的不足之处是性能和序列化大小,在性能和大小敏感的领域,往往会采用更为精简高效的二进制方式,如ProtoBuf、Thrift、MessagePack等。

至此,关于Java的标准序列化机制就介绍完了。我们介绍了它的用法和基本原理,最后分析了它的特点,它是一种神奇的机制,通过简单的Serializable接口就能自动处理很多复杂的事情,但它也有一些重要的限制,最重要的是不能跨语言。

5分钟搞定kkFileView 4.4.0 Docker部署:小白也能上手的完整配置流程 本文提供了一份针对kkFileView 4.4.0版本的Docker部署完整指南。通过使用预构建的Docker镜像,即使是新手也能在5分钟内完成文件预览服务的搭建,涵盖从环境准备、镜像拉取、容器启动到服务验证的全流程,并分享了进阶配置与生产环境优化建议。 阅读详情

相关推荐

LaTeX新手入门以及TeXlive和TeXstudio的安装使用

作为一个新手,LaTeX的了解和使用很是费了一番功夫,因此,在这里给找不到路的同志们稍微介绍一下。如有错误,敬请指正。本篇内容基于Win10系统,因此安装在Mac或者Linux上的同学可以再找找其他的介绍文章。 目录 LaTeX介绍 LaTeX环境配置 一、TeXlive下载 二、TeXlive安装 1. 安装main installer 2. 正式安装 三、TeXstudio安装...

zywhehe的博客 21万+

Java 文件高级技术 标准序列化机制

这里写目录标题14.4 标准序列化机制14.4.1 基本用法14.4.2 复杂对象14.4.3 定制序列化14.4.4 序列化的基本原理14.4.5 版本问题14.4.6 序列化特点分析参考目录 14.4 标准序列化机制         在前面几节,我们在将对象保存到文件时,使用的是 DataOutputStream,从文件读入对象时,使用的是DataInputStream,使用它们,需要逐个处理对象中的每个字段,我们提到,这种

norang的博客 420

在Unity3d使用yolov8的onnx模型

输出OUTPUTS是float32[1,35,8400],表示它会输出一个大小为[1, 35, 8400]的浮点数张量。这个输出张量的维度表示了检测到的目标的数量、每个目标的预测参数以及网格单元的数量。输出张量的第一个维度1表示检测到的目标数量。每个目标由35个值组成,表示目标的类别、置信度、边界框的坐标及10个特定点的位置。要取得8400个数据中置信度最高的数据,然后获取该数据其他channel的数据。运行结果:(蓝色是检测手的边框,红点是自定义的点),其中的四个数字分别表示不同的维度。

shenqingyu0605202324的博客 5603

《疯狂java讲义》学习(41):标准序列化机制

标准序列化机制 简单来说,序列化就是将对象转化为字节流,反序列化就是将字节流转化为对象。 基本用法 要让一个类支持序列化,只需要让这个类实现接口java.io.Serializable。Serializable没有定义任何方法,只是一个标记接口。比如,对于前面章节提到的Student类,为支持序列化,可改为: public class Student implements Serializable...

泛泛之素 731

重装系统后,恢复Oracle的.ORA文件

前几天,系统莫名奇妙产生很多宏病毒,进而感染整个C盘,不得已,格式化后重装了系统。 重装系统之后,突然想到D盘中的oracle中很多数据没导出来备份,安装文件下有很多有用的.ORA文件,抱着死马当活马医的态度,居然成功了。 1、将原来D盘(D:\oracle\product\10.2.0\oradata\orcl)下的数据文件备份下其他盘,将D盘的oracle目录名改成oracle1。安装数据

zhanggnol的blog 5551

ST MotorControl Workbench 6.2.1 自定义硬件配置避坑指南

本文详细介绍了ST MotorControl Workbench 6.2.1在自定义硬件配置中的常见问题与解决方案,涵盖功率板配置、采样电路校准、电机参数设置及代码生成避坑指南,帮助开发者高效完成电机控制项目开发。

red88的博客 1024

为什么序列化要实现Serializable接口

通过Serializable接口和ObjectOutputStream/ObjectInputStream类,Java标准库提供了一种方便的方式来实现对象的序列化和反序列化,使得对象可以在不同的环境中进行传输、存储和恢复。实现了Java中的Serializable接口的类会被称为可序列化的,这意味着它们的实例可以被序列化为字节流,以便于在网络上传输、保存到文件中或者在内存中进行持久化存储。需要注意的是,JVM本身并不执行序列化的具体逻辑,而是依赖于Java标准库中提供的序列化机制来完成序列化操作。

tzdiyouda的博客 847

【WRFDA第一期】WRF数据同化概述及资源整理

【WRFDA第一期】WRF数据同化概述及资源整理

WW、forever的博客 1660

Java 中的序列化与反序列化

在Java中, 序列化是一种把java对象状态转化成字节码流形式的机制, 这种机制被应用在RPC, Hibernate, RMI, JPA, EJB 和 JMS等场景中; 序列化的逆向操作是反序列化, 顾名思义, 就是把java对象的字节码流还原到jvm中的运行时对象; 常见的序列化方式 Standard Java Serialization API(java平台标志API) Json ...

江无羡 9148

10.2 全栈 CRUD 工程结构搭建:Cursor 4 步初始化 + 3 层目录规范

揭示 Cursor 工程初始化中 4 个关键动作如何将 AI 上下文感知能力从 30% 提升至 85%,包括用 pnpm create 显式声明结构契约、通过 monorepo.config.json 设定 workspace 根以支持跨包类型引用、在 shared 包中固化共享类型与协议,以及后续

weixin_42545292的博客 67

学Simulink——基于Simulink的扰动观测器(DOB)补偿坡道重力分量

(质量5kg,坡度30°,目标速度1m/s)为例,基于Simulink搭建“DOB+PI”复合控制仿真平台,从系统建模、DOB算法推导、离散化实现到Simulink模块搭建,提供模块化模型、核心代码及参数整定表,帮助工程师彻底解决重力等恒值扰动引起的稳态误差问题。采用双线性变换(Tustin): s=Ts​2​z+1z−1​,其中 Ts​为控制周期。模块相加(注意符号:ufinal​=upi​−ucomp​或 upi​+ucomp​,取决于扰动定义方向,此处假设重力为正,控制力需反向抵消,故为减)。

MHD0815的博客 69

序列化与反序列化

序列化的意义   在考虑系统性能的时候,会考虑序列化。远程通信的时候,就要考虑序列化。序列化和反序列化是每天都会碰到的问题。就我而言,序列化这个概念基本上一直在听到,但是很少有了解。Java对象的传输、分布式架构、大数据量的工程。   Java 平台允许我们在内存中创建可复用的Java 对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,所以这些对象的生命周期不会比JVM...

dingding163126163的博客 141

22、忆阻器、忆阻系统及其神经形态应用综述

本文综述了忆阻器与忆阻系统的理论基础及其在神经形态系统中的广泛应用。从忆阻器的基本定义出发,介绍了其在非线性电路、存储技术、通信和神经形态计算等领域的应用潜力。重点探讨了基于忆阻器的神经元模型(如Hodgkin-Huxley、FitzHugh-Nagumo和Hindmarsh-Rose模型)、突触实现方式及多种忆阻神经网络架构,包括细胞神经网络、脉冲神经网络和递归神经网络等。此外,深入分析了忆阻器在水库计算中的理论研究与硬件实现进展,展示了其在时间序列处理、模式识别和信号预测等方面的优异性能。尽管忆阻技术前

kiwi8的博客 207

序列化

一、序列化的用途 序列化的主要用途有两个,一个是对象持久化,另一个是跨网络的数据交换、远程过程调用。 二、各种序列化机制比较 (跨语言、序列化大小、性能)1、(000)Java标准的序列化机制有很多优点,使用简单,可自动处理对象引用和循环引用,也可以方便的进行定制,处理版本问题等,但它也有一些重要的局限性:  Java序列化格式是一种私有格式,是一种Java语言特有的技术,不能被其他语言识别...

weixin_30553837的博客 110

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Java 对象序列化

要使一个对象可以序列化,必须实现Serializable或者Externalizable标记接口 使用对象流(ObjectInputStream/ObjectOutputStream)可以读写序列化对象 一.序列化对象 普通java对象的序列化 先定义一个普通的可序列化对象 public class Person implements Serializable{     priva

The Hot Java 487

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

一文带你了解Java的I O机制

本文给出了I/O和N/IO的具体概念和示例。大家感兴趣可以将代码复制到本地,跑一跑,尝试运行。通过调试的方式,理解java的I/O的使用。

郝老三~~~~~~~~~~~~~ 814

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

内置序列化技术

本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的过程! 在这里查看 ! 目录 1.简介 2.可序列化的接口 3.可外部化的界面 4.有关可序列化接口的更多信息 5.可序列化和远程方法调用(RMI) 6. JAXB 7. ...

最佳 Java 编程 114

android对象序列化,Android序列化总结

前言公园里,一位仙风鹤骨的老者在打太极,一招一式都仙气十足,一个年轻人走过去:“大爷,太极这玩意儿花拳绣腿,你练它干啥?”老者淡淡一笑:“年轻人,你还没有领悟到太极的真谛,这样,你用最大力气打我试试。”于是年轻人用力打了老头一拳,被讹了八万六。从段子就能看出来,今天这篇博客又是一碗炒冷饭。序列化使用很简单,但是其中的一些细节并不是所有人都清楚。在日常的应用开发中,我们可能需要让某些对象离开内存空间...

weixin_33509141的博客 500

Java对象的序列化与反序列化

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34233856的博客 95

分布式架构(基础篇)——序列化与反序列化V.2.0.0

1.了解序列化的意义 Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 JVM 的生命周期更长。但在现实应用中,就可能要求在 JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java 对象序列化就能够帮助我们实现该功能 序列化是把对象的状态信息转化为可存储或...

qq_30509055的博客 321

Java IO流体系与序列化

Java IO流 一.Java I/O 流      — I/O 即 Input / Output ,完成输入和输出      — 要访问文件的内容,必须使用 I/O流      — 流的分类:    1. 按流的方向来分(从程序所在的内存的角度来看):       — 输入流:把外部输入读入当前程序所在内存       — 输出流:把当前程序所在内存的数据写到外部    2. 按

CrystalDestiny的专栏 1162

java 序列化 构造函数,Java序列化库不需要任何参数构造函数和实现Serializable

Is there is any way in java-world to serialize without need of no-arg constructors and implementation of Serializable?解决方案JBoss Serialization is a drop-in replacement for standard java serialization, ...

weixin_39975055的博客 215

为什么Fastjson 序列化会出现中 “$ref“ 引用机制问题的深度解析

Fastjson 的 $ref 引用机制是处理对象循环引用和重复引用的特色功能。当序列化对象图中存在共享引用时,Fastjson 会生成类似 {"$ref":"$.path"} 的结构以避免数据冗余和无限递归。特殊情况下可能出现哈希值形式的引用(如 $.e1aeee9d...),这是基于对象 identityHashCode 的唯一标识。开发者可通过 SerializerFeature.DisableCircularReferenceDetect 完全禁用引用,或改用

洛阳泰山的博客 1056

自定义序列化

package File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import j...

weixin_30783629的博客 126

Hive SerDe 机制原理:序列化 / 反序列化组件工作流程与自定义 SerDe 开发

SerDe(Serializer/Deserializer)是 Hive 处理结构化数据的核心组件: 以处理 JSON 数据为例: 通过 传递参数:

2501_93931267的博客 289

带你快速了解Hadoop的序列化

一、为什么要序列化 一般来说,“活的”对象只生存在内存里,关机断电就没有了。而且“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。 然而序列化可以存储“活的” 对象,可以将“活的”对象发送到远程计算机。 二、什么是序列化 序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储(持久化)和网络传输。 反序列化就是将收到字节序列(或其他数据传输协议)或者是硬盘的持...

IT Baicn的博客 221
上一篇: Java——内存映射文件
下一篇: Java——synchronized
吴声子夜歌
博客等级 码龄9年 2688粉丝 2270原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值