本文由 简悦 SimpRead 转码, 原文地址 cloud.tencent.com

序列化介绍

序列化是将变量转换为可保存或传输的字符串的过程;
反序列化就是在适当的时候把这个字符串再转化成原来的变量使用。

其实将数据序列化的作用无外乎有两个:

  • 方便传输
  • 方便存储

方便存储如何理解呢?

如:存储对象或者数组到数据库
比如我们有个 PHP 对象或者一个 PHP 数组需要存储到数据库甚至文件中,这显然是不可能的,这个时候必须要将 PHP 对象或者 PHP 数组序列化后再执行存储操作。
将对象直接存储起来,用的时候只需要简单的反序列化后就可以投产使用了,避免了 new 一次带来的性能耗费。

方便传输如何理解呢?

其实序列化在传输中应用的相对更多更常见些许。最简单的一个例子,一个码前端的码了一个 ajax 找你给 TA 提供一个 API,那么这个时候你俩得商量返回什么数据,比如 json 或者 xml,甚至你俩自己作死约定私有数据格式。比如在一个比较典型的服务架构中,网关服务器和内部
RPC 服务器之间通过 msgpack 或者 protobuf传递数据。这都是典型的序列化为了传输的典型应用案例。

这里序列化的概念可能更为广泛和笼统一些,包括传统的 serialize、json、msgpack、protobuf 等。

数据的序列化的类别

文本序列化

更好的可读性

  • 常见如 json、serialize、xml 等

    二进制序列化

    序列化的速度快,序列化后数据小

  • 常见如 msgpack、protobuf、thrift 等

序列化技术指标

一般说来,考验序列化技术的性能指标一共有两个,一个是序列化的速度,一个是序列化后数据的大小,自然是序列化速度越快、序列化后的数据越小为佳。

就目前来看,protobuf、msgpack 等二进制序列化无论是速度上还是数据大小上,都要比文本序列化更好。不过话说回来,文本序列化有更好的可读性

一、PHP 序列化变量的 4 种方法

1. serialize 和 unserialize 函数

这两个是序列化和反序列化 PHP 中数据的常用函数。

  1. <?php
  2. $a = array('a' => 'Apple' ,'b' => 'banana' , 'c' => 'Coconut');
  3. //序列化数组
  4. $s = serialize($a);
  5. echo $s;
  6. //输出结果:a:3:{s:1:"a";s:5:"Apple";s:1:"b";s:6:"banana";s:1:"c";s:7:"Coconut";}
  7. echo '<br /><br />';
  8. //反序列化
  9. $o = unserialize($s);
  10. print_r($o);

当数组值包含如双引号、单引号或冒号等字符时,它们被反序列化后,可能会出现问题。为了克服这个问题,一个巧妙的技巧是使用 base64_encode 和 base64_decode。

$obj = array();
//序列化
$s = base64_encode(serialize($obj)); 
//反序列化
$original = unserialize(base64_decode($s));

但是 base64 编码将增加字符串的长度。为了克服这个问题,可以和 gzcompress 一起使用。

//定义一个用来序列化对象的函数

function my_serialize( $obj ) 
{ 
  return base64_encode(gzcompress(serialize($obj))); 
} 

//反序列化
function my_unserialize($txt) 
{ 
  return unserialize(gzuncompress(base64_decode($txt))); 
}

2. json_encode 和 json_decode

使用 JSON 格式序列化和反序列化是一个不错的选择:

  • json_encode 序列化对象会失败
  • JSON 格式是可读的。
  • JSON 格式比 serialize 返回数据结果小。
  • JSON 格式是开放的、可移植的。其他语言也可以使用它。
$a = array('a' => 'Apple' ,'b' => 'banana' , 'c' => 'Coconut');

//序列化数组
$s = json_encode($a);
echo $s;
//输出结果:{"a":"Apple","b":"banana","c":"Coconut"}

echo '<br /><br />';

//反序列化
$o = json_decode($s);

在上面的例子中,json_encode 输出长度比上个例子中 serialize 输出长度显然要短。

3. var_export 和 eval

var_export 函数把变量作为一个字符串输出;eval 把字符串当成 PHP 代码来执行,反序列化得到最初变量的内容。

$a = array('a' => 'Apple' ,'b' => 'banana' , 'c' => 'Coconut');

//序列化数组
$s = var_export($a , true);
echo $s;
//输出结果: array ( 'a' => 'Apple', 'b' => 'banana', 'c' => 'Coconut', )

echo '<br /><br />';

//反序列化
eval('$my_var=' . $s . ';');

print_r($my_var);

4. wddx_serialize_value 和 wddx deserialize

wddx_serialize_value 函数可以序列化数组变量,并以 XML 字符串形式输出。

$a = array('a' => 'Apple' ,'b' => 'banana' , 'c' => 'Coconut');

//序列化数组
$s = wddx_serialize_value($a);
echo $s;

//输出结果(查看输出字符串的源码):<wddxPacket version='1.0'><header/><data><struct><var name='a'><string>Apple</string></var><var name='b'><string>banana</string></var><var name='c'><string>Coconut</string></var></struct></data></wddxPacket>

echo '<br /><br />';

//反序列化
$o = wddx_deserialize($s);

print_r($o);
//输出结果:Array ( [a] => Apple [b] => banana 1 => Coconut )

可以看出,XML 标签字符较多,导致这种格式的序列化还是占了很多空间。

结论

上述所有的函数在序列化数组变量时都能正常执行,但运用到对象就不同了。例如 json_encode 序列化对象就会失败。反序列化对象时,unserialize 和 eval 将有不同的效果。

英文原文:3 ways to serialize variables in php

PHP序列化对比

今天带到这里的这里的有四个具体的方案,这四种方案都是简单粗暴、开箱即用类型的,我们分别测试感受下,看哪个更适合我们。

其中前三个都是文本类型的,msgpack 则是二进制类型的。

PHP 内置的 serialize

serialize 用法 serialize(),序列化方法。
unserialize(),反序列化方法。

PHP 内置的 JSON 解析器、

json 用法
json_encode() 编码
json_decode() 解码

PHP 扩展 JSOND

JSOND 作为 PHP 内置的 JSON 解析器的高级版本,坊间一直传闻速度上要比内置的更牛 X 一些,作为扩展,这货需要额外安装,附送地址:https://pecl.php.net/get/jsond-1.4.0.tgz

jsond_encode(),和json_encode()一样,后面多个字母d而已。
jsond_decode(),和json_decode()一样,后面多个字母d而已。

PHP 扩展 msgpack。

msgpack 是一个鸟哥等人搞的一套二进制序列化工具,slogan 就是 “It’s like JSON.but fast and small.”,附送地址:https://pecl.php.net/get/msgpack-2.0.2.tgz

msgpack_pack(),序列化方法。
msgpack_unpack(),反序列化方法。

测试代码如下:

<?php
// 故意搞了一个还算大的php数组,更容易看出差距来
$arr = array(
    array(
        'uid' => 22193123,
        'gender' => 'famale',
        'username' => 'elarity',
        'password' => md5('www123'),
        'relation' => array(
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
            array(
                'uid' => 22193123,
                'gender' => 'famale',
                'username' => 'elarity',
                'password' => md5('www123'),
            ),
        ),
    )
);


// 每种序列化方案都执行100000次
$counter = 100000;


// json序列化方案,执行100000次
echo PHP_EOL.PHP_EOL;
$start = microtime( true );
for( $i = 1; $i <= $counter; $i++ ){
 $json = json_encode( $arr ); 
}
$size = strlen( $json );
$end = microtime( true );
$cost_time = $end - $start;
echo "json_encode : 耗费时间为{$cost_time} , 数据体积为{$size}".PHP_EOL;



// jsond序列化方案,执行100000次
$start = microtime( true );
for( $i = 1; $i <= $counter; $i++ ){
 $jsond = jsond_encode( $arr ); 
}
$size = strlen( $jsond );
$end = microtime( true );
$cost_time = $end - $start;
echo "jsond_encode : 耗费时间为{$cost_time} , 数据体积为{$size}".PHP_EOL;



// serialize序列化方案,执行100000次
$start = microtime( true );
for( $i = 1; $i <= $counter; $i++ ){
 $serialize = serialize( $arr ); 
}
$size = strlen( $serialize );
$end = microtime( true );
$cost_time = $end - $start;
echo "serialize : 耗费时间为{$cost_time} , 数据体积为{$size}".PHP_EOL;



// msgpack序列化方案,执行100000次
$start = microtime( true );
for( $i = 1; $i <= $counter; $i++ ){
 $msgpack = msgpack_pack( $arr );
}
$size = strlen( $msgpack );
$end = microtime( true );
$cost_time = $end - $start;
echo "msgpack耗费时间为 : {$cost_time} , 数据体积为{$size}".PHP_EOL;
echo PHP_EOL.PHP_EOL;

将文件保存为 test.php,然后 php test.php 执行,结果如下图所示:

image.png

总结一下:

  1. jsond 确实是要比 json 快一些的,数据体积一样
  2. 测试发现,serialize比json快,但是serialize() 数据体积确实大(因为还保留了数据类型说明)
  3. msgpack 最佳