序列化

翻译数据结构或对象状态在稍后的同一台或另一台计算机环境可以存储和重建的格式的过程

序列化(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的軟體包。

参考文献

外部連結