Home  >  Article  >  Backend Development  >  PHP序列化 serialize 格局详解

PHP序列化 serialize 格局详解

WBOY
WBOYOriginal
2016-06-20 13:01:161092browse

1.前言

PHP(从 PHP 3.05 开端)为保管对象提供了一组序列化和反序列化的函数:serialize、unserialize。不过在 PHP 
手册中对这两个函数的标明仅限于如何运用,而对序列化结果的格式却没做任何标明。因而,这对在其他言语中完成 PHP 
方式的序列化来说,就比拟费事了。虽然以前也搜集了一些其他言语完成的 PHP 
序列化的顺序,不过这些完成都不完全,当序列化或反序列化一些比拟庞杂的对象时,就会出错了。于是我决议写一份关于 PHP 
序列化格式详解的文档(也就是这一篇文档),以便在编写其他言语完成的 php 
序列化顺序时能有一个比拟完壁的参考。这篇文章中所写的内容是我议决编写顺序测试和阅读 PHP 源代码得到的,所以,我无法 100% 
保证一切的内容都是正确的,不过我会尽量保证我所写下的内容的正确性,关于我还不太清楚的地点,我会在文中明白指出,也期盼群众可以给予补充和完备。

2.概述

PHP 序列化后的内容是容易的文本格式,但是对字母大小写和空白(空格、回车、换行等)敏感,并且字符串是依照字节(或许说是 8 
位的字符)计算的,因而,更适宜的说法是 PHP 
序列化后的内容是字节流格式。因而用其他言语完成时,假设所完成的言语中的字符串不是字节贮存格式,而是 Unicode 
贮存格式的话,序列化后的内容不适宜保管为字符串,而应保管为字节流对象或许字节数组,否则在与 PHP 执行数据交流时会发生错误。

PHP 对不一样类型的数据用不一样的字母执行标示,Yahoo 开发站点提供的 Using Serialized PHP with Yahoo! Web Services 一文中给出一切的字母标示及其意思:

 

Word-WRAP: break-word" bgColor=#ddedfb>
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string

N 表示的是 NULL,而 b、d、i、s 表示的是四种标量类型,现在其它言语所完成的 PHP 序列化顺序基本上都完成了对这些类型的序列化和反序列化,不过有一些完成中对 s (字符串)的完成存在疑问。

a、O 属于最常用的复合类型,大局部其他言语的完成都很好的完成了对 a 的序列化和反序列化,但对 O 只完成了 PHP4 中对象序列化格式,而没有提供对 PHP 5 中扩展的对象序列化格式的支持。

r、R 区分表示对象援用和指针援用,这两个也比拟有用,在序列化比拟庞杂的数组和对象时就会发生带有这两个标示的数据,后面我们将细致解说这两个标示,现在这两个标示尚没有觉察有其他言语的完成。

C 是 PHP5 中引入的,它表示自定义的对象序列化方式,虽然这关于其它言语来说是没有必要完成的,由于很少会用到它,但是后面照旧会对它执行细致解说的。

U 是 PHP6 中才引入的,它表示 Unicode 编码的字符串。由于 PHP6 中提供了 Unicode 
方式保管字符串的才干,因而它提供了这种序列化字符串的格式,不过这个类型 PHP5、PHP4 
都不支持,而这两个版本现在是主流,因而在其它言语完成该类型时,不推荐用它来执行序列化,不过可以完成它的反序列化流程。在后面我也会对它的格式执行标
明。

开头尚有一个 o,这也是我独一还没弄清楚的一个数据类型标示。这个标示在 PHP3 中被引入用来序列化对象,但是到了 PHP4 现在就被 O
取代了。在 PHP3 的源代码中可以看到对 o 的序列化和反序列化与数组 a 基本上是一样的。但是在 PHP4、PHP5 和 PHP6 
的源代码中序列化局部里都找不到它的影子,但是在这多个版本的反序列化顺序源代码中却都有对它的处理,不过把它处理成什么我还没弄清楚。因而对它暂时不再作更多标明了。

3.NULL 和标量类型的序列化

NULL 和标量类型的序列化是最容易的,也是构成契合类型序列化的基本。这局部内容相信许多 PHP 开发者都以前熟知。假设您觉得以前掌握了这局部内容,可以直接跳过这一章。

3.1.NULL 的序列化

在 PHP 中,NULL被序列化为:

N;

3.2.boolean 型数据的序列化

boolean型数据被序列化为:

b:;

其中 为 0 或 1,当 boolean 型数据为 false 时, 为 0,否则为 1。

3.3.integer 型数据的序列化

integer型数据(整数)被序列化为:

i:;

其中 为一个整型数,范围为:-2147483648 到 
2147483647。数字前可以有正负号,假设被序列化的数字超越这个范围,则会被序列化为浮点数类型而不是整型。假设序列化后的数字超越这个范围
(PHP 自身序列化时不会发作这个疑问),则反序列化时,将不会前往希冀的数值。

3.4.double 型数据的序列化

double型数据(浮点数)被序列化为:

d:;

其中 为一个浮点数,其范围与 PHP 
中浮点数的范围一样。可以表示成整数方式、浮点数方式和迷信技术法方式。假设序列化无量大数,则 为 
INF,假设序列化负无量大,则 为 -INF。序列化后的数字范围超越 PHP 
能表示的最大值,则反序列化时前往无量大(INF),假设序列化后的数字范围超越 PHP 所能表示的最小精度,则反序列化时前往 0。

3.5.string 型数据的序列化

string型数据(字符串)被序列化为:

s::"";

其中 的长度, 
是非负整数,数字前可以带有正号(+)。 为字符串值,这里的每个字符都是单字节字符,其范围与 ASCII 码的 0 - 
255 的字符相对应。每个字符都表示原字符意思,没有转义字符, 两边的引号("")是必需的,但不计算在 
当中。这里的 相当于一个字节流,而  
是这个字节流的字节个数。

4.容易复合类型的序列化

PHP 中的复合类型有限组(array)和对象(object)两种,本章首要简介在容易情况下这两品种型数据的序列化格式。关于嵌套定义的复合类型和自定义序列化方式的对象的序列化格式将在后面的章节细致讨论。

4.1.数组的序列化

数组(array)通常被序列化为:

a::{...}

其中 表示数组元素的个数,…… 
表示数组下标,…… 表示与下标相对应的数组元素的值。

下标的类型只好是整型或许字符串型,序列化后的格式跟整型和字符串型数据序列化后的格式类似。

数组元素值可以是随意类型,其序列化后的格式与其所对应的类型序列化后的格式类似。

4.2.对象的序列化

对象(object)通常被序列化为:

O::" name>"::{ name 2>... n>}

其中 表示对象的类名 的字符串长度。 
表示对象中的字段1个数。这些字段包括在对象所在类及其祖先类中用 var、public、protected 和 private 
声明的字段,但是不包括 static 和 const 声明的静态字段。也就是说只需实例(instance)字段。

…… n>表示每个字段的字段名,而 …… value n> 则表示与字段名所对应的字段值。

字段名是字符串型,序列化后格式与字符串型数据序列化后的格式类似。

字段值可以是随意类型,其序列化后的格式与其所对应的类型序列化后的格式类似。

但字段名的序列化与它们声明的可见性是相关的,下面重点讨论一下关于字段名的序列化。

4.3.对象字段名的序列化

var 和 public 声明的字段都是公共字段,因而它们的字段名的序列化格式是类似的。公共字段的字段名依照声明时的字段名执行序列化,但序列化后的字段名中不包括声明时的变量前缀符号 $。

protected 声明的字段为维护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因而维护字段的字段名在序列化时,字段名先面会加上 \0*\0的前缀。这里的 \0 表示 ASCII 码为 0 的字符,而不是 \0 组合。

private 声明的字段为个体字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因而个体字段的字段名在序列化时,字段名先面会加上 \0\0的前缀。这里 表示的是声明该个体字段的类的类名,而不是被序列化的对象的类名。由于声明该个体字段的类不用需是被序列化的对象的类,而有能够是它的祖先类。

字段名被作为字符串序列化时,字符串值中包括依据其可见性所加的前缀。字符串长度也包括所加前缀的长度。其中 \0 字符也是计算长度的。

1注

在 PHP 手册中,字段被称为属性,而实践上,在 PHP 5 中引入的用 __set、__get 来定义的对象成员更适宜叫做属性。由于用 
__set、__get 来定义的对象成员与其它言语中的属性的行为是一致,而 PHP 
手册中所说的属性实践上在其他言语中(比如:C#)中被称为字段,为了防止混杂,这里也称为字段,而不是属性。

5.嵌套复合类型的序列化

上一章讨论了容易的复合类型的序列化,群众会觉察关于容易的数组和对象原本也很容易。但是假设遇到自己包括自己或许 A 包括 B,B 又包括 A 这类的对象或数组时,PHP 又该如何序列化这种对象和数组呢?本章我们就来讨论这种情况下的序列化方式。

5.1.对象援用和指针援用

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

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

<?php
echo "<pre class="brush:php;toolbar:false">";
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 "
"; ?>

这个例子的输出结果是这样的:
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="brush:php;toolbar:false">";
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 "
";

群众会觉察,运转结果也许出乎你的意料:
object(SampleClass)#1 (1) {
   ["value"]=>
   int(1)
}
int(1)

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

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

echo "<pre class="brush:php;toolbar:false">";
$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 "
";

结果是:

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)。
 

5.2.援用标示后的数字

在上面的例子中群众能够以前看到了,对象援用(r)和指针援用(R)的格式为:
r:;
R:;

群众必需很奇异后面这个 是什么吧?本节我们就来细致讨论这个疑问。

这个 容易的说,就是所援用的对象在序列化串中第一次呈现的位置,但是这个位置不是指字符的位置,而是指对象(这里的对象是泛指一切类型的量,而不只限于对象类型)的位置。

我想群众能够还不是很明白,那么我来举例标明一下:

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);

这个例子的结果是:

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 标示后面的数字,就是这个位置。

5.3.对象援用的反序列化

PHP 在反序列化处理对象援用时很有意思,假设反序列化的字符串不是 PHP 的 serialize() 自身生成的,而是人为构造或许用其它言语生成的,即便对象援用指向的不是一个对象,它也可以正确地依照对象援用所指向的数据执行反序列化。比如:

echo "<pre class="brush:php;toolbar:false">";
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 "
";

运转结果:
object(StrClass)#1 (2) {
["a"]=>
string(5) "Hello"
["b"]=>
string(5) "Hello"
}

群众会觉察,上面的例子反序列化后,$a->b 的值与 $a->a 的值是一样的,虽然 $a->a 
不是一个对象,而是一个字符串。因而假设群众用其它言语来完成序列化的话,不用需非要把 string 
作为标量类型来处理,即便依照对象援用来序列化拥有类似字符串内容的复合类型,用 PHP 
一样可以正确的反序列化。这样可以更节省序列化后的内容所占用的空间。

6.自定义对象序列化

6.1.PHP 4 中自定义对象序列化

PHP 4 中提供了 __sleep 和 __wakeup 这两个方法来自定义对象的序列化。不过这两个函数并不改动对象序列化的格式,影响的仅仅是被序列化字段的个数。关于它们的简介,在 PHP 手册中写的还算比拟细致。这里就不再多做简介了。

6.2.PHP 5 中自定义对象序列化

PHP 5 中添加了接口(interface)功用。PHP 5 自身提供了一个 Serializable 
接口,假设用户在自己定义的类中完成了这个接口,那么在该类的对象序列化时,就会被依照用户完成的方式去执行序列化,并且序列化后的标示不再是 
O,而改为 C。C 标示的格式如下:

C::""::{}

其中 表示类名 的长度, length> 表示自定义序列化数据 的长度,而自定义的序列化数据  
是完全的用户自己定义的格式,与 PHP 序列化格式可以完全没关,这局部数据由用户自己完成的序列化和反序列化接口方法来维护。

Serializable 接口中定义了 2 个方法,serialize() 和 unserialize($data),
两个方法不会被直接调用,而是在调用 PHP 序列化函数时,被自动调用。其中 serialize 函数没有参数,它的前往值就是 
的内容。而 unserialize($data) 有一个参数 $data,这个参数的值就是  
的内容。这样群众应该就明白了,实践上接口中 serialize 方法就是让用户来自己序列化对象中的内容,序列化后的内容格式,PHP 
并不关怀,PHP 只担任把它充填到 中,等到反序列化时,PHP 只担任取出这局部内容,然后传给用户完成的 
unserialize($data) 接口方法,让用户自己去反序列化这局部内容。

下面举个容易的例子,来标明 Serializable 接口的运用:

class MyClass implements Serializable
{
public $member;

function MyClass()
{
$this->member = 'member value';
}

public function serialize()
{
return wddx_serialize_value($this->member);
}

public function unserialize($data)
{
$this->member = wddx_deserialize($data);
}
}
$a = new MyClass();
echo serialize($a);
echo "\n";
print_r(unserialize(serialize($a)));

因而假设想用其它言语来完成 PHP 序列化中的 C 标示的话,也须要提供一种这样的机制,让用户自定义类时,可以自己在反序列化时处理 内容,否则,这些内容就无法被反序列化了。

7.Unicode 字符串的序列化

好了,开头再谈谈 PHP 6 中关于 Unicode 字符串序列化的疑问吧。

说真话,我不如何喜好把字符串搞成双字节 Unicode 这种编码的东西。javascript中也是用这样的字符串,因而在处理字节流的东西时,反而十分的不简约。C# 
虽然也是用这种方式来编码字符串,不过还好的是,它提供了周到的编码转换机制,并且提供这种字符串到字节流(实践上是到字节数组)的转换,所以处理起来还
算是可以。但是关于不熟识这个的人来说,转来转去就是个费事。

PHP 6 之前不时是按字节来编码字符串的,到了 PHP 6 突然冒出个 Unicode 编码的字符串来,虽然是可选的,但仍然让人觉得十分不温馨,假设配置不当,老的顺序兼容性都成疑问。

当然加了这个东西现在,许多老的与字符串相关的函数都执行了修正。序列化函数也不例外。因而,PHP 6 中添加了专门的 Unicode 字符串序列化标示 U。PHP 6 中对 Unicode 字符串的序列化格式如下:

U::"";

这里 是指原 Unicode String 的长度,而不是 的长度,由于 是阅历编码现在的字节流了。

但是尚有一点要留意, 虽然是原 Unicode String 
的长度,但是也不是只它的字节数,当然也不完全是指它的字符数,确切的说是之它的字符单位数。由于 Unicode String 中采用的是 
UTF16 编码,这种编码方式运用 16 位来表示一个字符的,但是并不是一切的都是可以用 16 位表示的,因而有些字符须要两个 16 
位来表示一个字符。因而,在 UTF16 编码中,16 
位字符算作一个字符单位,一个实践的字符能够就是一个字符单位,也有能够由两个字符单位组成。因而, Unicode String 
中字符数并不总是等于字符单位数,而这里的 指的就是字符单位数,而不是字符数。

又是怎样被编码的呢?实践上,它的编码也很容易,关于编码小于 128 的字符(但不包括 
\),依照单个字节写入,关于大于 128 的字符和 \ 字符,则转化为 16 进制编码的字符串,以 \ 
作为开头,后面四个字节区分是这个字符单位的 16 进制编码,顺序依照由高位到低位陈列,也就是第 16-13 
位所对应的16进制数字字符(abcdef 这多个字母是小写)作为第一个字节,第 12-9 位作为第二个字节,第 8-5 
位作为第三个字节,开头的第 4-1 位作为第四个字节。顺次编码下来,得到的就是 的内容了。

我以为关于其他言语来说,没有必要完成这种序列化方式,由于用这种方式序列化的内容,关于现在的主流 PHP 服务器来说都是不支持的,不过倒是可以完成它的反序列化,这样未来即便跟 PHP 6 执行数据交流,也可以够相互读懂了。


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn