.net序列化及反序列化

序列化是指一个对象的实例可以被保存,保存成一个二进制串,当然,一旦被保存成二进制串,那么也可以保存成文本串了。
比如,一个计数器,数值为2,我们可以用字符串“2”表示。
如果有个对象,叫做connter,当前值为2,那么可以序列化成“2”,反向的,也可以从“2”得到值为2的计数器实例。
这样,关机时序列化它,开机时反序列化它,每次开机都是延续的。不会都是从头开始。

序列化概念的提出和实现,可以使我们的应用程序的设置信息保存和读取更加方便。

序列化有很多好处,比如,在一台机器上产生一个实例,初始化完毕,然后可以序列化,通过网络传送到另一台机器,然后反序列化,得到对象实例,之后再执行某些业务逻辑,得到结果,再序列化,返回第一台机器,第一台机器得到对象实例,得到结果。
这个例子是目前比较先进的“智能代理”的原理。

当前比较热火的web services使用soap协议,soap协议也是以对象的可序列化为基础的。

一 概述
.NET Framework为处理XML数据提供了许多不同的类库。XmlDocument类能让你像处理文件一样处理xml数据,而XmlReader、XmlWriter和它们的派生类使你能够将xml数据作为数据流处理。
XmlSerializer则提供了另外的方法,它使你能够将自己的对象串行化和反串行化为xml。串行化数据既能够让你像处理文件一样对数据进行随机处理,同时又能跳过你不感兴趣的数据。
二 主要类库介绍
   .NET 支持对象xml序列化和反序列化的类库主要位于命名空间System.Xml.Serialization中。
   1.  XmlSerializer 
   该类用一种高度松散耦合的方式提供串行化服务。你的类不需要继承特别的基类,而且它们也不需要实现特别的接口。相反,你只需在你的类或者这些类的公共域以及读/写属性里加上自定义的特性。XmlSerializer通过反射机制读取这些特性并用它们将你的类和类成员映射到xml元素和属性。
   2. XmlAttributeAttribute 
   指定类的公共域或读/写属性对应xml文件的Attribute。
   例:[XmlAttribute(“type”)] or [XmlAttribute(AttributeName=”type”)]
   3. XmlElementAttribute
   指定类的公共域或读/写属性对应xml文件的Element。
   例:[XmlElement(“Maufacturer”)] or [XmlElement(ElementName=”Manufacturer”)]
   4. XmlRootAttribute
   Xml序列化时,由该特性指定的元素将被序列化成xml的根元素。
   例:[XmlRoot(“RootElement”)] or [XmlRoot(ElementName = “RootElements”)]
   5. XmlTextAttribute 
   Xml序列化时,由该特性指定的元素值将被序列化成xml元素的值。一个类只允许拥有一个该特性类的实例,因为xml元素只能有一个值。
   6. XmlIgnoreAttribute
   Xml序列化时不会序列化该特性指定的元素。
三 实例
   下面例子中的xml schema 描述了一个简单的人力资源信息,其中包含了xml的大部分格式,如xml 元素相互嵌套, xml元素既有元素值,又有属性值。
   1. 待序列化的类层次结构
    [XmlRoot("humanResource")]
    public class HumanResource
    {
        #region  private data.
        private int m_record = 0;
        private Worker[] m_workers = null;
        #endregion
 
        [XmlAttribute(AttributeName="record")]
        public int Record
        {
            get { return m_record; }
            set { m_record = value; }
        }
 
        [XmlElement(ElementName="worker")]
        public Worker[] Workers
        {
            get { return m_workers; }
            set { m_workers = value; }
        }
}
 
    public class Worker
    {
        #region  private data.
        private string m_number = null;
        private InformationItem[] m_infoItems = null;
        #endregion
 
        [XmlAttribute("number")]
        public string Number
        {
            get { return m_number; }
            set { m_number = value; }
        }
 
        [XmlElement("infoItem")]
        public InformationItem[] InfoItems
        {
            get { return m_infoItems; }
            set { m_infoItems = value; }
        }
}
 
    public class InformationItem
    {
        #region  private data.
        private string m_name = null;
        private string m_value = null;
        #endregion
 
        [XmlAttribute(AttributeName = "name")]
        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }
 
        [XmlText]
        public string Value
        {
            get { return m_value; }
            set { m_value = value; }
        }
}
   2. 序列化生成的xml结构
         <?xml version="1.0" ?>
-  < humanResource  xmlns:xsi =" http://www.w3.org/2001/XMLSchema-instance " xmlns:xsd =" http://www.w3.org/2001/XMLSchema "  record =" 2 ">
-  < worker number =" 001 ">
                   < infoItem name =" name "> Michale </ infoItem >
                   < infoItem name =" sex "> male </ infoItem >
                   < infoItem name =" age "> 25 </ infoItem >
              </ worker >
-  < worker number =" 002 ">
                   < infoItem name =" name "> Surce </ infoItem >
                   < infoItem name =" sex "> male </ infoItem >
                   < infoItem name =" age "> 28 </ infoItem >
             </ worker >
           </ humanResource >
   3. 利用XmlSerializer类进行序列化和反序列化的实现(一般利用缓存机制实现xml文件只解析一次。)
     public  sealed class ConfigurationManager
    {
        private static HumanResource m_humanResource = null;
        private ConfigurationManager(){}
 
        public static HumanResource Get(string path)
        {
            if (m_humanResource == null)
            {
                FileStream fs = null;
                try
                {
                    XmlSerializer xs = new XmlSerializer(typeof(HumanResource));
                    fs = new FileStream(path, FileMode.Open, FileAccess.Read);
                    m_humanResource = (HumanResource)xs.Deserialize(fs);
                    fs.Close();
                    return m_humanResource;
                }
                catch
                {
                    if (fs != null)
                        fs.Close();
                    throw new Exception("Xml deserialization failed!");
                }
 
            }
            else
            {
                return m_humanResource;
            }
        }
 
        public static void Set(string path, HumanResource humanResource)
        {
            if (humanResource == null)
                throw new Exception("Parameter humanResource is null!");
           
            FileStream fs = null;
            try
            {
                XmlSerializer xs = new XmlSerializer(typeof(HumanResource));
                fs = new FileStream(path, FileMode.Create, FileAccess.Write);
                xs.Serialize(fs, humanResource);
                m_humanResource = null;
                fs.Close();
            }
            catch
            {
                if (fs != null)
                    fs.Close();
                throw new Exception("Xml serialization failed!");
            }
        }
    }
四 说明
   1. 需要序列化为xml元素的属性必须为读/写属性;
   2. 注意为类成员设定缺省值,尽管这并不是必须的。

    “序列化”可被定义为将对象的状态存储到存储媒介中的过程。在此过程中,对象的公共字段和私有字段以及类的名称(包括包含该类的程序集)都被转换为字节流,然后写入数据流。在以后“反序列化”该对象时,创建原始对象的精确复本。
一、为什么要选择序列化
    一个原因是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本
    另一个原因是通过值将对象从一个应用程序域发送到另一个应用程序域中
    例如,序列化可用于在 ASP.NET 中保存会话状态并将对象复制到 Windows 窗体的剪贴板中。远程处理还可以使用序列化通过值将对象从一个应用程序域传递到另一个应用程序域中。
二、如何实现对象的序列化及反序列化
    要实现对象的序列化,首先要保证该对象可以序列化。而且,序列化只是将对象的属性进行有效的保存,对于对象的一些方法则无法实现序列化的。
    实现一个类可序列化的最简便的方法就是增加Serializable属性标记类。如:
    [Serializable()]
    public class MEABlock
    {
        private int m_ID;
        public string Caption;

        public MEABlock()
        {
            ///构造函数
        }
    }

    即可实现该类的可序列化。
    要将该类的实例序列化为到文件中?.NET FrameWork提供了两种方法:
    1、XML序列化
        使用 XmLSerializer 类,可将下列项序列化。

  • 公共类的公共读/写属性和字段
  • 实现 ICollection 或 IEnumerable 的类。(注意只有集合会被序列化,而公共属性却不会。)
  • XmlElement 对象。
  • XmlNode 对象。
  • DataSet 对象。

         要实现上述类的实例的序列化,可参照如下例子:
        MEABlock myBlock = new MEABlock();
        // Insert code to set properties and fields of the object.
        XmlSerializer mySerializer = new XmlSerializer(typeof(MEABlock));
        // To write to a file, create a StreamWriter object.
        StreamWriter myWriter = new StreamWriter("myFileName.xml");
        mySerializer.Serialize(myWriter, MEABlock);

    需要注意的是XML序列化只会将public的字段保存,对于私有字段不予于保存。
    生成的XML文件格式如下:
        
<MEABlock>
            <Caption>Test</Caption>
        </MEABlock>
    对于对象的反序列化,则如下:
        
MEABlock myBlock;
        
// Constructs an instance of the XmlSerializer with the type
        // of object that is being deserialized.

        XmlSerializer mySerializer = new XmlSerializer(typeof(MEABlock));
        // To read the file, creates a FileStream.
        FileStream myFileStream = new FileStream("myFileName.xml", FileMode.Open);
        
// Calls the Deserialize method and casts to the object type.
        myBlock = (MEABlock)mySerializer.Deserialize(myFileStream)

    2、二进制序列化
        与XML序列化不同的是,二进制序列化可以将类的实例中所有字段(包括私有和公有)都进行序列化操作。这就更方便、更准确的还原了对象的副本。
        要实现上述类的实例的序列化,可参照如下例子:
        MEABlock myBlock = new MEABlock();
        // Insert code to set properties and fields of the object.
        IFormatter formatter = new BinaryFormatter();
        Stream stream = new FileStream("MyFile.bin",FileMode.Create,FileAccess.Write, FileShare.None);
        formatter.Serialize(stream, myBlock);
        stream.Close();

    对于对象的反序列化,则如下:
        IFormatter formatter = new BinaryFormatter();
        Stream stream = new FileStream("MyFile.bin", FileMode.Open,FileAccess.Read, FileShare.Read);
        MEABlock myBlock = (MEABlock) formatter.Deserialize(stream);
        stream.Close();

三、如何变相实现自定义可视化控件的序列化、反序列化
        对于WinForm中自定义控件,由于继承于System.Windows.Form类,而Form类又是从MarshalByRefObject继承的,窗体本身无法做到序列化,窗体的实现基于Win32下GUI资源,不能脱离当前上下文存在。
        当然可以采用变通的方法实现控件的序列化。这里采用的是记忆类模型
        定义记忆类(其实就是一个可序列化的实体类)用于记录控件的有效属性,需要序列化控件的时候,只需要将该控件的实例Copy到记忆类,演变成序列化保存该记忆类的操作。
        反序列化是一个逆过程。将数据流反序列化成为该记忆类,再根据该记忆类的属性生成控件实例。而对于控件的一些事件、方法则可以继续使用。

 
.NET Framework 开发员指南  
序列化是将对象转换为容易传输的格式的过程。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象。在另一端,反序列化将从该流重新构造对象。

XML 序列化仅将对象的公共字段和属性值序列化为 XML 流。XML 序列化不包括类型信息。例如,如果您有一个存在于 Library 命名空间中的 Book 对象,将不能保证它将会被反序列化为同一类型的对象。

注意   XML 序列化不转换方法、索引器、私有字段或只读属性(只读集合除外)。若要序列化对象的所有字段和属性(公共的和私有的),请使用 BinaryFormatter,而不要使用 XML 序列化。

XML 序列化中最主要的类是 XmlSerializer 类,它的最重要的方法是 Serialize 和 Deserialize 方法。XmlSerializer 生成的 XML 流符合万维网联合会 (www.w3.org) XML 架构定义语言 (XSD) 1.0 的建议。另外,生成的数据类型符合标题为“XML Schema Part 2: Datatypes”(XML 架构第二部分:数据类型)的文档。

对象中的数据是用编程语言构造(如类、字段、属性、基元类型、数组,甚至 XmlElement 或 XmlAttribute 对象形式的嵌入 XML)来描述的。您可以创建自己的用属性批注的类,或者使用 XML 架构定义工具生成基于现有 XML 架构的类。

如果您有 XML 架构,就可运行 XML 架构定义工具生成一组强类型化为架构并用属性批注的类。当序列化这样的类的实例时,生成的 XML 符合 XML 架构。使用这样的类,就可针对容易操作的对象模型进行编程,同时确保生成的 XML 符合 XML 架构。这是使用 .NET Framework 中的其他类(如 XmlReader 和 XmlWriter 类)分析和写 XML 流的一种替换方法。(有关使用这些类的更多信息,请参见使用 .NET Framework 中的 XML。)这些类使您可以分析任何 XML 流。与此相反,当需要 XML 流符合已知的 XML 架构时,请使用 XmlSerializer

属性控制由 XmlSerializer 类生成的 XML 流,使您可以设置 XML 流的 XML 命名空间、元素名、属性名等。有关这些属性和它们如何控制 XML 序列化的更多信息,请参见使用属性控制 XML 序列化。若想获得一个列示对所生成的 XML 起控制作用的那些属性的表,请参见控制 XML 序列化的属性

XmlSerializer 类可进一步序列化对象并生成编码的 SOAP XML 流。生成的 XML 符合标题为“Simple Object Access Protocol (SOAP) 1.1”的万维网联合会文档的第 5 节。有关此过程的更多信息,请参见用 XML 序列化生成 SOAP 消息。有关控制生成的 XML 的属性表,请参见控制编码的 SOAP 序列化的属性

XmlSerializer 类生成由 XML Web services 创建和传递给 XML Web services 的 SOAP 消息。若要控制 SOAP 消息,可将属性应用于 XML Web services 文件 (.asmx) 中的类、返回值、参数和字段。您可以同时使用在“控制 XML 序列化的属性”中列出的属性和在“控制编码的 SOAP 序列化的属性”中列出的属性,因为 XML Web services 可以使用文本样式,也可以使用编码的 SOAP 样式。有关使用属性控制 XML Web services 所生成的 XML 的更多信息,请参见 XML Web services 的 XML 序列化。有关 SOAP 和 XML Web services 的更多信息,请参见自定义 SOAP 消息

保护 XmlSerializer 应用程序

在创建使用 XmlSerializer 的应用程序时,您应当了解以下几点以及它们的影响:

  • XmlSerializer 创建 C# 文件 (.cs 文件),并将其编译成 .dll 文件,这些 .dll 文件位于由 TEMP 环境变量指定的目录中;这些 DLL 文件将发生序列化。

    代码和 DLL 在创建和进行编译时,易于遭受恶意进程的攻击。如果所使用的计算机运行的是 Microsoft Windows NT 4.0 或更高版本,则有可能会有两个或更多用户共享临时目录。如果同时存在以下两种情况,则共享临时目录是有危险性的:(1) 两个帐户有不同的安全特权;(2) 具有较高特权的帐户运行一个使用 XmlSerializer 的应用程序。在这种情况下,某一用户可以替换所编译的 .cs 或 .dll 文件,由此破坏计算机的安全性。为了避免发生这一问题,请始终确保计算机上的每一帐户都有自己的配置文件。如果能够保证这一点的话,默认情况下,TEMP 环境变量就会为不同的帐户指定不同的目录。

  • 如果一名恶意用户向 Web 服务器发送持续的 XML 数据流(拒绝服务攻击),XmlSerializer 会一直处理这一数据,直到计算机资源不够用才停止。

    如果您所使用的计算机运行 Internet 信息服务 (IIS),并且您的应用程序是在 IIS 下运行,就可以避免这类攻击。IIS 带有一个控制门,用于禁止处理大于设定数量(默认值是 4 KB)的数据流。如果您所创建的应用程序不使用 IIS,同时该应用程序使用 XmlSerializer 进行反序列化,则应该实现一个类似的控制门,以阻止拒绝服务攻击。

  • XmlSerializer 将使用给予它的任何类型,对数据进行序列化,并运行任何代码。

    恶意对象施加威胁的方式有两种。一种是运行恶意代码,另一种是将恶意代码插入到由 XmlSerializer 创建的 C# 文件中。在第一种情况下,如果恶意对象试图运行破坏性过程,代码访问安全性将帮助防止发生任何破坏。在第二种情况下,在理论上,恶意对象有可能会以某种方式将代码插入到由 XmlSerializer 创建的 C# 文件中。尽管对这一问题已进行了彻底的检验,而且这类攻击被认为是不可能的,但您还是应该小心一些,一定不要序列化那些不可信的未知类型的数据。

  • 已序列化的重要数据可能易于遭到攻击。

    XmlSerializer 对数据进行了序列化之后,数据可以被存储为 XML 文件,或存储在其他数据存储区。如果其他进程可以访问到您的数据存储区,或是可以在 Intranet 或 Internet 上看到该数据存储区,数据就可能被窃取,并被恶意使用。例如,如果您创建了一个应用程序,对包含信用卡号码的订单进行序列化,这一数据就非常重要。为了防止发生这一问题,请始终保护您的数据存储区,并对数据采取保密措施。

简单类的序列化

下面的示例显示一个具有公共字段的简单类:

[Visual Basic]Public Class OrderForm    Public OrderDate As DateTimeEnd Class[C#]public class OrderForm{    public DateTime OrderDate;}

当将此类的实例序列化时,该实例可能类似于下面的代码:

<OrderForm>    <OrderDate>12/12/01</OrderDate></OrderForm>

有关序列化的更多示例,请参见 XML 序列化的示例

可以序列化的项

使用 XmLSerializer 类,可将下列项序列化。

  • 公共类的公共读/写属性和字段
  • 实现 ICollection 或 IEnumerable 的类。(注意只有集合会被序列化,而公共属性却不会。)
  • XmlElement 对象。
  • XmlNode 对象。
  • DataSet 对象。

序列化和反序列化对象

若要序列化对象,首先创建要序列化的对象并设置它的公共属性和字段。为此,必须确定要用以存储 XML 流的传输格式(或者作为流,或者作为文件)。例如,如果 XML 流必须以永久形式保存,则创建 FileStream 对象。当您反序列化对象时,传输格式确定您将创建流还是文件对象。确定了传输格式之后,就可以根据需要调用 Serialize 或Deserialize 方法。

序列化对象

  1. 创建对象并设置它的公共字段和属性。
  2. 使用该对象的类型构造 XmlSerializer。有关更多信息,请参见 XmlSerializer 类构造函数。
  3. 调用 Serialize 方法以生成对象的公共属性和字段的 XML 流表示形式或文件表示形式。下面的示例创建一个文件。
    [Visual Basic]Dim myObject As MySerializableClass = New MySerializableClass()' Insert code to set properties and fields of the object.Dim mySerializer As XmlSerializer = New XmlSerializer(GetType(MySerializableClass))' To write to a file, create a StreamWriter object.Dim myWriter As StreamWriter = New StreamWriter("myFileName.xml")mySerializer.Serialize(myWriter, myObject)[C#]MySerializableClass myObject = new MySerializableClass();// Insert code to set properties and fields of the object.XmlSerializer mySerializer = new XmlSerializer(typeof(MySerializableClass));// To write to a file, create a StreamWriter object.StreamWriter myWriter = new StreamWriter("myFileName.xml");mySerializer.Serialize(myWriter, myObject);

反序列化对象

  1. 使用要反序列化的对象的类型构造 XmlSerializer
  2. 调用 Deserialize 方法以产生该对象的副本。在反序列化时,必须将返回的对象强制转换为原始对象的类型,如下面的示例中所示。下面的示例将该对象反序列化为文件,尽管它也可以被反序列化为流。
    [Visual Basic]Dim myObject As MySerializableClass' Constructs an instance of the XmlSerializer with the type' of object that is being deserialized.Dim mySerializer As XmlSerializer = New XmlSerializer(GetType(MySerializableClass))' To read the file, creates a FileStream.Dim myFileStream As FileStream = _New FileStream("myFileName.xml", FileMode.Open)' Calls the Deserialize method and casts to the object type.myObject = CType( _mySerializer.Deserialize(myFileStream), MySerializableClass)[C#]MySerializableClass myObject;// Constructs an instance of the XmlSerializer with the type// of object that is being deserialized.XmlSerializer mySerializer = new XmlSerializer(typeof(MySerializableClass));// To read the file, creates a FileStream.FileStream myFileStream = new FileStream("myFileName.xml", FileMode.Open);// Calls the Deserialize method and casts to the object type.myObject = (MySerializableClass) mySerializer.Deserialize(myFileStream)

有关 XML 序列化的更多示例,请参见 XML 序列化的示例

使用 XML 序列化的好处

XmlSerializer 类在您将对象序列化为 XML 时为您提供完整而灵活的控制。如果您正在创建 XML Web services,则可以将控制序列化的属性应用于类和成员以确保 XML 输出符合特定的架构。

例如,XmlSerializer 使您能够:

  • 指定应将字段或属性编码为特性还是元素。
  • 指定要使用的 XML 命名空间。
  • 如果字段或属性名不合适,则指定元素或特性的名称。

XML 序列化的另一个好处是:只要生成的 XML 流符合给定的架构,则对于所开发的应用程序就没有约束。假定有这样一个用于描述图书的架构,它具有标题、作者、出版商和 ISBN 编号元素。您可以开发一个以您希望的任何方式(例如,作为图书订单,或作为图书清单)处理 XML 数据的应用程序。在任一种情况下,唯一的要求是 XML 流应当符合指定的 XML 架构定义语言 (XSD) 架构。

XML 序列化注意事项

使用 XmlSerializer 类时,应考虑下列情况:

  • 序列化数据只包含数据本身以及类的结构。不包括类型标识和程序集信息。
  • 只能序列化公共属性和字段。如果需要序列化非公共数据,请使用 BinaryFormatter 类而不是 XML 序列化。
  • 类必须有一个将由 XmlSerializer 序列化的默认构造函数。
  • 不能序列化方法。
  • XmlSerializer 可以以不同方式处理实现 IEnumerable 或 ICollection 的类(条件是这些类满足某些要求)。实现 IEnumerable 的类必须实现带单个参数的公共 Add方法。Add 方法的参数必须与从 GetEnumerator 方法返回的 IEnumerator.Current 属性所返回的类型一致(多态)。除实现 IEnumerable 外还实现 ICollection的类(如 CollectionBase)必须有一个取整数的公共 Item 索引属性(在 C# 中为索引器),并且它必须有一个整数类型的公共 Count 属性。传递给 Add 方法的参数必须与从 Item 属性返回的类型相同或与该类型的某个基的类型相同。对于实现 ICollection 的类,要序列化的值将从索引 Item 属性检索,而不是通过调用GetEnumerator 来检索。另外请注意,除返回另一个集合类(实现 ICollection 的集合类)的公共字段之外,将不序列化公共字段和属性。有关示例,请参见 XML 序列化的示例

XSD 数据类型映射

标题为“XML Schema Part 2: Datatypes”的万维网联合会 (www.W3.org) 文档指定在 XML 架构定义语言 (XSD) 架构中允许使用的简单数据类型。对于这些数据类型中的许多类型(例如,int 和 decimal),在 .NET Framework 中都有对应的数据类型。但是,某些 XML 数据类型在 .NET Framework 中没有对应的数据类型(例如,NMTOKEN 数据类型)。在这样的情况下,如果使用 XML 架构定义工具 (Xsd.exe) 从架构生成类,就会将适当的特性应用于字符串类型的成员,并会将其DataType 属性设置为 XML 数据类型名称。例如,如果架构包含一个数据类型为 XML 数据类型 NMTOKEN 的、名为“MyToken”的元素,则生成的类可能包含以下示例中的成员。

[Visual Basic]<XmlElement(DataType:="NMTOKEN")>Public MyToken As String[C#][XmlElement(DataType = "NMTOKEN")]public string MyToken;

与此类似,如果创建一个必须符合特定 XML 架构 (XSD) 的类,应当应用适当的特性并将它的 DataType 属性设置为所需的 XML 数据类型名称。

有关类型映射的完整列表,请参见下列任意一个特性类的 DataType 属性:SoapAttributeAttributeSoapElementAttributeXmlArrayItemAttributeXmlAttributeAttributeXmlElementAttribute 或 XmlRootAttribute

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值