跳转到内容

序列化

本页使用了标题或全文手工转换
维基百科,自由的百科全书

序列化(serialization)在计算机科学的资料处理中,是指将数据结构对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。

序列化计算机科学中通常有以下定义:

用途

[编辑]
  • 经由电信线路传输资料的方法(通信)。
  • 存储资料的方法(在数据库或硬盘)。
  • 远程程序调用的方法,例如在SOAP中。
  • 在以组件为基础,例如COMCORBA的软件工程中,是对象的分布式方法。
  • 检测随时间资料变动的方法。

为了达成上述功能其一能有效作用,则必须与硬件结构保持独立性。譬如说为了能最大化分布式的使用,在不同硬件运行的计算机,应该能够可靠地重建序列化资料流,而不依赖于字节序。虽然直接复拷存储器中的数据结构更简便又快速,可是对于其它不同硬件的机器,却无法可靠地运作。以独立于硬件之外的格式来序列化数据结构,要避开字节序、存储器布局、或在不同编程语言中数据结构如何表示等等之类的问题。

对于任何序列化方案的本质来说,因为资料编码是根据定义连续串在一起的,提取序列化数据结构中的某一部分,则需要从头到尾读取整个对象并且重新建构。这样的资料线性在许多应用中是有利的,因为它使输出入接口简单而共同,能被用来保持及传递对象的状态。

要求高性能的应用时,花费精力处理更复杂的非线性存储系统是有其必要意义的。即使在单一机器上,原始的指针对象也非常脆弱无法保存,因为它们指向的标地可能重新加载到内存中的不同地址。为了处理这个问题,序列化过程包括一个步骤:将引用的直接指针转换为以名称或位置的间接引用,称之为不挥发(unswizzling)或者指针不挥发。反序列化过程则包括了称为指针旋转(swizzling)的反向步骤。由于序列化和反序列化可从共通代码(例如,微软MFC中的Serialize函数)驱动,所以共通代码可同时进行两次,因此,

  1. 检测要序列化的对象与其先前副本之间的差异,
  2. 提供下一次这种检测的输入。因为差异可以被即时检测,所以不必再重新建立先前的副本。该技术称为差异分辨执行。

这技术应用在内容随时间变化的用户界面编程中-依照输入事件来处理图形对象的产生、移除、更改或制作,而无需编写另外的代码执行这些操作。

缺点

[编辑]

序列化可能会破解抽象资料类型的封装实现,而使其详细内容曝光。简单的序列化实现可能违反面向对象中私有资料成员需要封装(encapsulation)的原则。商用软件的出版商通常会将应用软件的序列化格式,当作商业秘密,以阻碍竞争对手生产可兼容的产品;有些会蓄意地混淆,或甚至将序列化资料作加密处理。然而,互通可用性的要求应用程序能够理解彼此的序列化格式。因此,像CORBA的远程方法调用架构详细定义了它们的序列化格式。许多机构,例如档案馆和图书馆,尝试将他们的备份文件-特别是数据库抛档(dump),存储成一些相对具可读性的序列化格式中,使备份资料不因信息技术变迁而过时。

序列化格式

[编辑]

20世纪80年代初的施乐网络系统快递技术影响了第一个广泛采用的标准。Sun Microsystems在1987年发布了外部数据表示法(XDR)。90年代后期开始推动标准序列化的协议:XML(可扩展标记语言)应用于产生人类可读的文字编码。资料以这样的编码使存续的对象能有效用,无论相对于人是否可阅读与理解,或与编程语言无关地传递给其它信息系统。它缺点是失去了扎实的编码字节流,但截至目前技术上所提供大量的存储和传输容量,使得文件大小的考量,已不同于早期计算机科学的重视程度。

二进制XML被提议作为一种妥协方式,它不能被纯文本编辑器读取,但比一般XML更为扎实。在二十一世纪的Ajax技术网页中,XML经常应用于结构化资料在客端和服务端之间的异步传输。相较于XML,JSON是一种轻量级的纯文字替代,也常用于网页应用中的客端-服务端通信。JSON肇基于JavaScript语法所派生,但也广为其它编程语言所支持。与JSON类似的另一个替代方案是YAML,它包含加强序列化的功能,更“人性化”而且更扎实。这些功能包括标记资料类型,支持非层次结构式数据结构,缩进结构化资料的选项以及多种形式的标量资料引用的概念。

另一种可读的序列化格式是属性列表(property list)。应用在NeXTSTEPGNUstepmacOS Cocoa环境中。

针对于科学使用的大量资料集合,例如气候,海洋模型和卫星数据,已经开发了特定的二进制序列化标准,例如HDFnetCDF和较旧的GRIB

编程语言支持

[编辑]

一些面向对象的编程语言直接支持对象序列化(或对象归档),可借由语法糖元素或者提供了标准接口。这些编程语言其中有Ruby,Smalltalk,Python,PHP,Objective-C,Delphi,Java 和.NET系列语言。若是缺少原生支持序列化的编程语言,也可使用额外的函数库来添加功能。

C/C++

[编辑]

C 和 C++ 没有提供任何类型的高阶序列化构造,但是两种语言都支持将内置资料类型以及一般的数据结构(struct)输出为二进制资料。因此,开发人员自己定义序列化函数是显而易举的。此外,基于编译器的解决方案,如用于 C++ 的ODB ORM系统,能够自动产生类声明的序列化源码,不必修改或仅少量的修改。其它普及的序列化框架是有来自Boost框架的Boost.Serialization,S11n和Cereal等框架。微软的MFC框架也提供序列化方法,作为其文件视图(Document-View)架构的部件。

Java

[编辑]

Java 提供自动序列化,需要以java.io.Serializable接口的实例来标明对象。实现接口将类别标明为“可序列化”,然后Java在内部处理序列化。在Serializable接口上并没有预先定义序列化的方法,但可序列化类别可任意定义某些特定名称和签署的方法,如果这些方法有定义了,可被调用执行序列化/反序列化部分过程。该语言允许开发人员以另一个Externalizable接口,更彻底地实现并覆盖序列化过程,这个接口包括了保存和恢复对象状态的两种特殊方法。

在默认情况下有三个主要原因使对象无法被序列化。其一,在序列化状态下并不是所有的对象都能获取到有用的语义。例如,Thread对象绑定到当前Java虚拟机的状态,对Thread对象状态的反序列化环境来说,没有意义。其二,对象的序列化状态构成其类别兼容性缔结(compatibility contract)的某一部分。在维护可序列化类别之间的兼容性时,需要额外的精力和考量。所以,使类别可序列化需要慎重的设计决策而非默认情况。其三,序列化允许访问类别的永久私有成员,包含敏感信息(例如,密码)的类别不应该是可序列化的,也不能外部化。上述三种情形,必须实现Serializable接口来访问Java内部的序列化机制。标准的编码方法将字段简单转换为字节流。

原生类型以及永久和非静态的对象引用,会被编码到字节流之中。序列化对象引用的每个对象,若其中未标明为transient的字段,也必须被序列化;如果整个过程中,引用到的任何永久对象不能序列化,则这个过程会失败。开发人员可将对象标记为暂时的,或针对对象重新定义的序列化,来影响序列化的处理过程,以截断引用图的某些部分而不序列化。Java并不使用构造函数来序列化对象。

JDBC也可对Java对象进行序列化,并将其存储到数据库中。虽然Swing组件的确实例化了Serializable接口,但它们不能移植到有版本差异的Java虚拟机之间。因此,Swing组件或任何继承它的组件可以序列化为字节数组,但不能保证这个仓存在另一台机器上可读取。

Perl

[编辑]

由CPAN所提供的几个Perl模块提供序列化机制,包括了StorableJSON::XSFreezeThawStorable包括将文件或Perl标量的数据结构,将其序列化和反序列化的功能。除了直接序列化到文件之外,Storable 还包含了冻结功能,将包装为标量的资料,返回其序列化的副本;并可用thaw(解冻)这个标量来反序列化。这对于以网络插座(socket)发送复杂的数据结构,或将其存储于数据库中非常有用。

当利用Storable对结构进行序列化时,具备了网络安全性的功能,它们以降低一点性能的成本,将资料存储为任何计算机可读取的格式。这些功能的名称有nstorenfreeze等。依硬件特定的,带字母“n”函数所序列化的这些结构,则以没有字母“n”的函数将之反序列化-常态地解冻并截取反序列化结构。

PHP

[编辑]

PHP最初通过内置的serialize()unserialize()函数来实现序列化。PHP可以序列化任何其它数据类型,除了资源(文件指针,socket等)以外。对不受信任的资料上使用内置的unserialize()函数时,通常是有风险的。对于对象有两种“魔术方法”,__sleep()__wakeup(),可以在类别中实现。而会分别从serialize()unserialize()中调用,对应于清理和恢复对象的功能。例如,在序列化时可能需要关闭数据库连线,并在反序列化时恢复连线;这个功能可在这两种魔术方法中处理。它们也允许对象选择哪些属性可被序列化。从PHP 5.1开始有面向对象的序列化机制,即为Serializable接口。

Python

[编辑]

Python编程核心的序列化机制是pickle标准函数库,这名称暗示数据库相关的特别术语“浸渍”,来描述资料反序列化(unpickling for deserializing)。Pickle 使用一个简单的基于堆栈的虚拟机来记录用于重建对象的指令。这是个跨版本并可自定义定义的序列化格式,但并不安全(不能防止错误或恶意资料)。错误格式或蓄意构建的资料,可能导致序列反解器导入任意模块,而且实例化任何对象。

这个函数库有另外包括序列化为标准资料格式的模块:json(内置的基本标量与集合类型支持,且能够通过编解码支持任何类型)和XML编码的属性列表(plistlib),限于plist支持的类型(数字,字符串,布尔,元组,串列,字典,日期时间和二进制blob)。最后,建议在正确的环境中评估对象的__repr__,使其和Common Lisp的打印对象大略地相符合。并非所有对象类型可以自动浸渍,特别是那些拥有操作系统资源(如文件把柄)的,但开发人员能注册自定义定义的“缩减”和构造功能,来支持任何类型的浸渍和序列化。

Pickle最初是纯粹以Python编程语言来实现的模块,但在Python 3之前的版本中,cPickle模块(也是内置的)提供了更快速的性能。cPickle从Unladen Swallow项目改造而成。在Python 3中,开发人员应该导入标准版本,该版本会尝试导入加速版本并返回纯Python版本。

.NET Framework

[编辑]

.NET框架有几个由微软设计的序列化器。第三方协力厂商也有许多序列化器。

Delphi

[编辑]

Delphi提供将组件(也称为持续对象)序列化的内置机制,完全与开发环境集成。组件的内容会被保存在DFM文件中,并即时重新加载。

OCaml

[编辑]

OCaml的标准函数库提供Marshal模块和Pervasives函数,output_valueinput_value用于编组。虽然OCaml编程是静态类型检查的,但Marshal模块的使用可能会破坏类型保证,因为没有方法能检查反序列的流,是否代表期望类型的对象。OCaml中的函数或含有函数的数据结构(例如带有方法的对象),由于其中的执行码不可以在相异程序之间传输,所以难以将函数编组。(有一个旗标可标示函数代码的位置,但只能在完全相同的程序中解组)。标准编组功能可以配置一个旗标,来共享和循环资料的处理。

Smalltalk

[编辑]

通常,非递归和非共享的对象能利用storeOn:/readFrom:协议,以人类可读的形式来存储和截取。storeOn:方法产生一个Smalltalk表达式原文,而以readFrom:评估时:重新建立原始对象。这方案特殊之处在于它利用对象的程序描述,而不是资料本身。因此它非常有弹性,允许更紧密的表示类定义。不过在其原始形式中,它不处理循环的数据结构,也不保留共享引用的识别(即两个引用对应到单一对象,将被恢复为两个相等的引用,但这两份是不同的副本)。

为此,存在各种可携和非便携式的代替方案。其中一些属于特定的Smalltalk实现或是类库。在Squeak Smalltalk中有几种方法可以序列化和存储对象。最简单和最常用的是storeOn:/readFrom:,和根基于SmartRefStream二进制单元格式的序列化程序。此外对于包裹对象,可以用ImageSegments来存储和截取。两者都提供了所谓的“二进制对象仓存框架”,可对紧密二的进制形式执行序列化和截取。两者都处理循环的、递归的和共享的结构,存储/截取类别和父类信息,并且包括用于“即时”迁移对象的机制(将旧版编写的实例,依照不同对象布局转换成类别)。

这些API(storeBinary/readBinary)虽然彼此相似,但编码细节是不同的,使得这两种格式并不兼容。而Smalltalk/X是自由开放源码的,能被加载到其它Smalltalks方言中,允许它们之间能互相交换。对象序列化并非ANSI Smalltalk规范的一部分。因此,序列化对象的代码因Smalltalk实现而异,所得到的二进制资料也不同。例如在Ambrai中就无法恢复在Squeak中所建立的序列化对象。所以,不同Smalltalk实现的各种应用程序,无法在不同实现之间共享资料。这些应用程序包括MinneStore对象数据库和一些RPC包。这个问题的解决方案是SIXX,它是一个使用XML格式进行序列化的Smalltalks的软件包。

参考文献

[编辑]

外部链接

[编辑]