序列化大概就是把一些變數轉化成為字串的位元組流的形式,這樣比較容易傳輸、儲存。當然,關是傳輸儲存沒有什麼,關鍵是變成串的形式以後還能夠轉化回來,而且能夠保持原來資料的結構。
在PHP中有多序列化處理的函數:serialize(),該函數把任何變數值(除了資源變數)轉化為字串的形式,可以把字串儲存到檔案裡,或者註冊為Session,乃至於使用curl來類比GET/POST來傳輸變數,達到RPC的效果。
如果要將序列化的變數轉化成PHP原始的變數值,那麼可以使用unserialize()函數。
一、變數序列化
我們舉簡單的例子來說明序列化,以及它的儲存格式。
整型:
$var = 23;
echo serialize($var);
輸出:
i:23;
浮點型:
$var = 1.23;
echo serialize($var);
輸出:
d:1.229999999999999982236431605997495353221893310546875;
字串:
$var = "This is a string";
echo serialize($var);
$var = "我是變數";
echo serialize($var);
輸出:
s:16:"This is a string";
s:8:"我是變數";
布爾型:
$var = true;
echo serialize($var);
$var = false;
echo serialize($var);
輸出:
b:1;
b:0;
上面這些基本類型序列化之後的情況很清楚,序列化之後的儲存格式是:
變數類型:[變數長度:]變數值;
就是第一位字元代表變數類型,第二個:代表分割,變數長度是可選的,就是在字串類型裡有,其他類型沒有,最後一個就是變數值,每個序列化的值以";"作為結束。
比如我們整型數字23序列化之後就是:i:23,那麼它沒有長度,只有類型和變數值,i代表integer,通過冒號分割,後面儲存的是整型值23,包括浮點型(雙位元組型)也是一樣。布爾型的話,類型是b(boolean),如果是true的話,那麼序列化的值是1,如果是false那麼值就是0。字
符串值話中間會多一個儲存的值得,儲存字串的長度值,比如字串"This is a string",那麼產生的序列化的值是 s:16:"This is a string"; s是string,代表類型,中間的16就是該字串的長度,如果是中文的話,那麼每個中文是兩個字元來儲存的,比如字串 "我是變數",產生的序列化值是:s:8:"我是變數"; 就是8個字元的長度。
下面我們重點來講一下陣列變數序列化。
陣列變數:
$var = array("abc", "def", "xyz", "123");
echo serialize($var);
輸出:
a:4:{i:0;s:3:"abc";i:1;s:3:"def";i:2;s:3:"xyz";i:3;s:3:"123";}
就是把我的數組 $var 序列化得到的字串值,我們的$var數組包括4個字串元素,分別是"abc", "def", "xyz", "123",我們來分析一下序列化後的資料,為了簡便起見,我們把序列化的資料列成數組的樣式:
a:4:
{
i:0;s:3:"abc";
i:1;s:3:"def";
i:2;s:3:"xyz";
i:3;s:3:"123";
}
這樣排列就比較清晰了,看開始的字串:a:4:{...} 首先第一個字元a儲存的是變數類型是array(數組)類型,第二個 4 儲存的是數組元素的個數,一共有4個,然後在{}之間數組元素的內容。比如第一個數組元素:i:0;s:3:"abc"; i代表是當前數組元素的索引實值型別是整型,並且值是 0,元素值的類型是s(字串的),個數是 3 個,具體值是"abc",分號結束,下面的數組元素依次類推。
我們再看看使用字串做為元素索引會如何:
$var = array("index1"=>"abc", "index2"=>"def", "index3"=>"xyz", "index4"=>"123");
echo serialize($var);
輸出:
a:4:{s:6:"index1";s:3:"abc";s:6:"index2";s:3:"def";s:6:"index3";s:3:"xyz";s:6:"index4";s:3:"123";}
變成數組樣式後:
a:4:
{
s:6:"index1";s:3:"abc";
s:6:"index2";s:3:"def";
s:6:"index3";s:3:"xyz";
s:6:"index4";s:3:"123";
}
其實跟上面沒有太大區別,不過是開始的索引變成了儲存字串的形式,比如第一個元素:s:6:"index1";s:3:"abc";第一項就是索引值:s:6:"index1"; s是類型,6是索引字串的長度,"index1"就是索引的值。後面的s:3:"abc"; 就是元素值,這個好理解,就不講了。
從上面來看,我們大致瞭解了基礎資料型別 (Elementary Data Type)的序列化,其實我們完全可以構造自己的序列化功能,或者從這個角度去擴充,開發自己的序列化程式,便於我們的變數交換。
當然,其實我們也可以利用這個功能,把數組或者任意其他變數序列化成字串,然後通過curl功能來類比GET/POST功能,達到能夠無用使用者執行動作就從遠程伺服器擷取資料的功能。
二、對象序列化
對象的序列化也是一個比較普遍的功能,能夠把一個對象進行序列化以後變成一個字串,能夠儲存或者傳輸。
我們先看一個例子:
class TestClass
{
var $a;
var $b;
function TestClass()
{
$this->a = "This is a";
$this->b = "This is b";
}
function getA()
{
return $this->a;
}
function getB()
{
return $this->b;
}
}
$obj = new TestClass;
$str = serialize($obj);
echo $str;
輸出結果:
O:9:"TestClass":2:{s:1:"a";s:9:"This is a";s:1:"b";s:9:"This is b";}
我們來分析一個對象序列化之後的字串。
O:9:"TestClass":2:
{
s:1:"a";s:9:"This is a";
s:1:"b";s:9:"This is b";
}
首先看對於對象本身的內容:O:9:"TestClass":2:O是說明這是一個物件類型(object),然後9是代表對象的名字查過濃度,2是代表該對象有幾個屬性。在看兩個屬性的內容:
s:1:"a";s:9:"This is a"; 其實跟數組的內容比較類似,第一項:s:1:"a"; 是描述屬性名稱的,第二項s:9:"This is a"; 是描述屬性值的。後面的屬性類似。
先說一種對象序列化的應用,下面的內容是PHP手冊上,沒有更改原文。
serialize() 返回一個字串,包含著可以儲存於 PHP 的任何值的位元組流表示。unserialize() 可以用此字串來重建原始的變數值。用序列化來儲存對象可以儲存對象中的所有變數。對象中的函數不會被儲存,只有類的名稱。
要能夠 unserialize() 一個對象,需要定義該對象的類。也就是,如果序列化了 page1.php 中類 A 的對象 $a,將得到一個指向類 A 的字串並包含有所有 $a 中變數的值。如果要在 page2.php 中將其解序列化,重建類 A 的對象 $a,則 page2.php 中必須要出現類 A 的定義。這可以例如這樣實現,將類 A 的定義放在一個包含檔案中,並在 page1.php 和 page2.php 都包含此檔案。
<?php
// classa.inc:
class A
{
var $one = 1;
function show_one()
{
echo $this->one;
}
}
// page1.php:
include("classa.inc");
$a = new A;
$s = serialize($a);
// 將 $s 存放在某處使 page2.php 能夠找到
$fp = fopen("store", "w");
fputs($fp, $s);
fclose($fp);
// page2.php:
// 為了正常解序列化需要這一行
include("classa.inc");
$s = implode("", @file("store"));
$a = unserialize($s);
// 現在可以用 $a 對象的 show_one() 函數了
$a->show_one();
?>
如果在用會話並使用了 session_register() 來註冊對象,這些對象會在每個 PHP 頁面結束時被自動序列化,並在接下來的每個頁面中自動解序列化。基本上是說這些對象一旦成為會話的一部分,就能在任何頁面中出現。
強烈建議在所有的頁面中都包括這些註冊的對象的類的定義,即使並不是在所有的頁面中都用到了這些類。如果沒有這樣做,一個對象被解序列化了但卻沒有其類的定義,它將失去與之關聯的類並成為 stdClass 的一個對象而完全沒有任何可用的函數,這樣就很沒有用處。
因此如果在以上的例子中 $a 通過運行 session_register("a") 成為了會話的一部分,應該在所有的頁面中包含 classa.inc 檔案,而不只是page1.php 和 page2.php。
當然,其實序列化對象其實完全可以應用在很多地方。當然,在PHP 5中對序列化的處理不一樣了,我們看一下手冊中的說法:
serialize() 檢查類中是否有魔術名稱 __sleep 的函數。如果這樣,該函數將在任何序列化之前運行。它可以清除對象並應該返回一個包含有該對象中應被序列化的所有變數名的數組。
使用 __sleep 的目的是關閉對象可能具有的任何資料庫連接,提交等待中的資料或進行類似的清除任務。此外,如果有非常大的對象而並不需要完全儲存下來時此函數也很有用。
相反地,unserialize() 檢查具有魔術名稱 __wakeup 的函數的存在。如果存在,此函數可以重建對象可能具有的任何資源。
使用 __wakeup 的目的是重建在序列化中可能丟失的任何資料庫連接以及處理其它重新初始化的任務。