教学之友,学习之友。

站长教学网

当前位置: 站长教学网 > 网站编程 > PHP教程 >

PHP序列化serialize之嵌套复合类型

时间:2012-12-26 15:29来源:未知 作者:ken 点击:

PHP serialize 序列化之嵌套复合类型的序列化,包括对象引用和指针引用、对象引用和指针引用的区别、引用标示后的数字、对象引用的反序列化。

嵌套复合类型的序列化

前面讨论了简单的复合类型的序列化,但是如果遇到自己包含自己或者 A 包含 B,B 又包含 A 这类的对象或数组时,PHP 又该如何序列化这种对象和数组呢?

1、对象引用和指针引用

在 PHP 中,标量类型数据是值传递的,而复合类型数据(对象和数组)是引用传递的。但是复合类型数据的引用传递和用 & 符号明确指定的引用传递是有区别的,前者的引用传递是对象引用,而后者是指针引用。

在解释对象引用和指针引用之前,先让我们看几个例子。

以下为引用内容:
<?php 
echo "<pre>"; 
class SampleClass { 
var $value; 
} 
$a = new SampleClass(); 
$a->value = $a;
$b = new SampleClass(); 
$b->value = &$b;
echo serialize($a); 
echo "\n"; 
echo serialize($b); 
echo "\n"; 
echo "</pre>"; 
?>

这个例子的输出结果是这样的:

以下为引用内容:
O:11:"SampleClass":1:{s:5:"value";r:1;} 
O:11:"SampleClass":1:{s:5:"value";R:1;}

大家会发现,这里变量 $a 的 value 字段的值被序列化成了 r:1,而 $b 的 value 字段的值被序列化成了 R:1。

但是对象引用和指针引用到底有什么区别呢?

大家可以看下面这个例子:

以下为引用内容:
echo "<pre>"; 
class SampleClass { 
var $value; 
} 
$a = new SampleClass(); 
$a->value = $a;
$b = new SampleClass(); 
$b->value = &$b;
$a->value = 1; 
$b->value = 1;
var_dump($a); 
var_dump($b); 
echo "</pre>"; 

大家会发现,运行结果也许出乎你的预料:

以下为引用内容:
object(SampleClass)#1 (1) {
["value"]=>
int(1)
}
int(1)

改变 $a->value 的值仅仅是改变了 $a->value 的值,而改变 $b->value 的值却改变了 $b 本身,这就是对象引用和指针引用的区别。

不过很不幸的是,PHP 对数组的序列化犯了一个错误,虽然数组本身在传递时也是对象引用传递,但是在序列化时,PHP 似乎忘记了这一点,看下面的例子:

以下为引用内容:
echo "<pre>"; 
$a = array(); 
$a[1] = 1; 
$a["value"] = $a;
echo $a["value"]["value"][1]; 
echo "\n"; 
$a = unserialize(serialize($a)); 
echo $a["value"]["value"][1]; 
echo "</pre>"; 

结果是:

1

大家会发现,将原数组序列化再反序列化后,数组结构变了。原本 $a["value"]["value"][1] 中的值 1,在反序列化之后丢失了,原因是什么呢?让我们输出序列化之后的结果来看一看:

以下为引用内容:
$a = array(); 
$a[1] = 1; 
$a["value"] = $a;
echo serialize($a); 

结果是:

以下为引用内容:
a:2:{i:1;i:1;s:5:"value";a:2:{i:1;i:1;s:5:"value";N;}}

原来,序列化之后,$a["value"]["value"] 变成了 NULL,而不是一个对象引用。也就是说,PHP 只对对象在序列化时才会生成对象引用标示(r)。对所有的标量类型和数组(也包括 NULL)序列化时都不会生成对象引用。但是如果明确使用了 & 符号作的引用,在序列化时,会被序列化为指针引用标示(R)。

[page]

2、引用标示后的数字

在上面的例子中大家可能已经看到了,对象引用(r)和指针引用(R)的格式为:

以下为引用内容:
r:<number>; 
R:<number>;

大家一定很奇怪后面这个 <number> 是什么吧?

这个 <number> 简单的说,就是所引用的对象在序列化串中第一次出现的位置,但是这个位置不是指字符的位置,而是指对象(这里的对象是泛指所有类型的量,而不仅限于对象类型)的位置。

我想大家可能还不是很明白,那么我来举例说明一下:

以下为引用内容:
class ClassA { 
var $int; 
var $str; 
var $bool; 
var $obj; 
var $pr; 
}
$a = new ClassA(); 
$a->int = 1; 
$a->str = "Hello"; 
$a->bool = false; 
$a->obj = $a; 
$a->pr = &$a->str;
echo serialize($a); 

这个例子的结果是:站长教学网 eduyo.com

以下为引用内容:
O:6:"ClassA":5:{s:3:"int";i:1;s:3:"str";s:5:"Hello";s:4:"bool";b:0;s:3:"obj";r:1;s:2:"pr";R:3;}

在这个例子中,首先序列化的对象是 ClassA 的一个对象,那么给它编号为 1,接下来要序列化的是这个对象的几个成员,第一个被序列化的成员是 int 字段,那它的编号就为 2,接下来被序列化的成员是 str,那它的编号就是 3,依此类推,到了 obj 成员时,它发现该成员已经被序列化了,并且编号为 1,因此它被序列化时,就被序列化成了 r:1; ,在接下来被序列化的是 pr 成员,它发现该成员实际上是指向 str 成员的一个引用,而 str 成员的编号为 3,因此,pr 就被序列化为 R:3; 了。

PHP 是如何来编号被序列化的对象的呢?实际上,PHP 在序列化时,首先建立一个空表,然后每个被序列化的对象在被序列化之前,都需要先计算该对象的 Hash 值,然后判断该 Hash 值是否已经出现在该表中了,如果没有出现,就把该 Hash 值添加到这个表的最后,返回添加成功。如果出现了,则返回添加失败,但是在返回失败前先判断该对象是否是一个引用(用 & 符号定义的引用),如果不是则也把 Hash 值添加到表后(尽管返回的是添加失败)。如果返回失败,则同时返回上一次出现的位置。

在添加 Hash 值到表中之后,如果添加失败,则判断添加的是一个引用还是一个对象,如果是引用,则返回 R 标示,如果是对象,则返回 r 标示。因为失败时,会同时返回上一次出现的位置,因此,R 和 r 标示后面的数字,就是这个位置。

[page]

3、对象引用的反序列化

PHP 在反序列化处理对象引用时很有意思,如果反序列化的字符串不是 PHP 的 serialize() 本身生成的,而是人为构造或者用其它语言生成的,即使对象引用指向的不是一个对象,它也能正确地按照对象引用所指向的数据进行反序列化。例如:

以下为引用内容:
echo "<pre>"; 
class StrClass { 
var $a; 
var $b; 
}
$a = unserialize('O:8:"StrClass":2:{s:1:"a";s:5:"Hello";s:1:"b";r:2;}');
var_dump($a); 
echo "</pre>"; 

运行结果:

以下为引用内容:
object(StrClass)#1 (2) { 
["a"]=> 
string(5) "Hello" 
["b"]=> 
string(5) "Hello" 
}

大家会发现,上面的例子反序列化后,$a->b 的值与 $a->a 的值是一样的,尽管 $a->a 不是一个对象,而是一个字符串。因此如果大家用其它语言来实现序列化的话,不一定非要把 string 作为标量类型来处理,即使按照对象引用来序列化拥有相同字符串内容的复合类型,用 PHP 同样可以正确的反序列化。这样可以更节省序列化后的内容所占用的空间。

(责任编辑:ken)
TAG标签: php 函数 serialize
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
注册登录:不允许匿名留言,登录后留言无需输入验证码。
栏目列表
最新内容