序列化

**序列化**（serialization）在計算機科學的資料處理中，是指將資料結構或物件狀態轉換成可取用格式（例如存成檔案，存於緩衝，或經由網路中傳送），以留待後續在相同或另一台計算機環境中，能恢復原先狀態的過程。依照序列化格式重新獲取位元組的結果時，可以利用它來產生與原始物件相同語義的副本。對於許多物件，像是使用大量參照的複雜物件，這種序列化重建的過程並不容易。物件導向中的物件序列化，並不概括之前原始物件所關聯的函式。這種過程也稱為物件編組（marshalling）。從一系列位元組提取資料結構的反向操作，是反序列化（也稱為解編組、deserialization、unmarshalling）。

**序列化**在電腦科學中通常有以下定義：

* 對同步控制而言，表示強制在同一時間內進行單一存取。
* 在數據儲存與傳送的部分是指將一個物件儲存至一個儲存媒介，例如檔案或是記憶體緩衝等，或者透過網路傳送資料時進行編碼的過程，可以是位元組或是 XML 等格式。而位元組的或 XML 編碼格式可以還原完全相等的物件。這程式被應用在不同應用程式之間傳送物件，以及伺服器將物件儲存到檔案或資料庫。相反的過程又稱為反序列化。

## 用途

* 經由電信線路傳輸資料的方法（通訊）。
* 儲存資料的方法（在資料庫或硬碟）。
* 遠端程式調用的方法，例如在 SOAP 中。
* 在以元件為基礎，例如 COM，CORBA 的軟體工程中，是物件的分散式方法。
* 檢測隨時間資料變動的方法。

為了達成上述功能其一能有效作用，則必須與硬體結構保持獨立性。譬如說為了能最大化分散式的使用，在不同硬體運行的計算機，應該能夠可靠地重建序列化資料流，而不依賴於位元組序。雖然直接複拷記憶體中的資料結構更簡便又快速，可是對於其它不同硬體的機器，卻無法可靠地運作。以獨立於硬體之外的格式來序列化資料結構，要避開位元組序、記憶體佈局、或在不同程式語言中資料結構如何表示等等之類的問題。

對於任何序列化方案的本質來說，因為資料編碼是根據定義連續串在一起的，提取序列化資料結構中的某一部份，則需要從頭到尾讀取整個物件並且重新建構。這樣的資料線性在許多應用中是有利的，因為它使輸出入介面簡單而共同，能被用來保持及傳遞物件的狀態。

要求高效能的應用時，花費精力處理更複雜的非線性儲存系統是有其必要意義的。即使在單一機器上，原始的指針物件也非常脆弱無法儲存，因為它們指向的標地可能重新載入到記憶體中的不同位址。為了處理這個問題，序列化過程包括一個步驟：將參照的直接指針轉換為以名稱或位置的間接參照，稱之為不揮發（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）。應用在 NeXTSTEP、GNUstep 和 macOS Cocoa 環境中。

針對於科學使用的大量資料集合，例如氣候，海洋模型和衛星數據，已經開發了特定的二進制序列化標準，例如 HDF，netCDF 和較舊的 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 模組提供序列化機制，包括了 `Storable`，`JSON::XS` 和 `FreezeThaw`。
`Storable` 包括將檔案或 Perl 純量的資料結構，將其序列化和反序列化的功能。除了直接序列化到檔案之外，`Storable` 還包含了凍結功能，將包裝為純量的資料，返回其序列化的副本；並可用 `thaw`(解凍) 這個純量來反序列化。這對於以網路插座（socket）發送複雜的資料結構，或將其儲存於資料庫中非常有用。

當利用 Storable 對結構進行序列化時，具備了網路安全性的功能，它們以降低一點效能的成本，將資料儲存為任何計算機可讀取的格式。這些功能的名稱有 `nstore`，`nfreeze` 等。依硬體特定的，帶字母「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_value` 和 `input_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 的軟體包。

## 參考文獻

## 外部連結