PHP serialize、json_encode、var_export 序列化的正確性與效率

這一篇嚴格上來說與 WordPress 沒有很強烈的相關,但其中要探討的問題,在客製化 WordPress 或是利用 php 開發程式都有很中強烈的關係。

於程式語言當中,我們有 布林值、整數、字串、陣列、物件 等等各式各樣的變數形態可以使用,但當我們要將這變數儲存起來使用的時候,如果你是存在資料庫當中,那缺少了陣列與物件這兩種形態可以使用。如果是要存入檔案當中,那更加的淒慘,因為只剩下字串這一個選項可以選擇了。
也因此就有所謂的序列化這樣的技巧,將各式各樣的變數轉換成文字已利儲存。
不過當一個變數儲存之後,相對的還要再拿來使用,所以可以正確的還原變數也是在選擇的時候很重要的一個基準。

選擇 serialize 會是最容易處理的。

var_export 會是最差的選擇,因為她轉出來的資料,雖然方便閱讀,但是並不容易再轉回去。根據測試只有數字與布林值有辦法。
json_encode 的表現好多了。文字與陣列都沒有問題,物件方面就有一點尷尬了,基本上只有類似 stdClass 這樣簡單到像是陣列的物件才有辦法處理,其於的物件就掛點啦。
serialize 這就是選安全的選擇啦。因為他連遇到物件也不會出錯。不單是 PHP 內建的一些物件,連我們自己設定的物件也不成問題。甚至是有繼承物件的物件,也可以順利破解。

不過聰明的,有沒有發現我在 serialize 的時候,是使用安全的做法,而不是最好的做法。
這時候就進入到標題當中的第二部分,效率的問題。
做網站寫程式,當然不能單單指考慮程式好不好寫的問題,效能也是我們所需要注意的議題。尤其是當你的網站發展到某一個規模之後。
結論就是 json_encode 的效能相較 serialize 至少有兩倍以上的效率,而 json_decode 也比 unserialize 的校能有 1.5 倍以上的速度。
因此,效能與通用性之間的選擇,就看你啦。
因為有時候可以預測資料的情況下,使用 json_encode 的方式會比 serialize 來的好多了。
不過如果對於可能的輸入變數形態是不確定的情況之下,那當然就要選擇通用的 serialize 來處理了。

serialize

形態 序列化值 不含形態驗證 包含形態驗證
bool b:1; TRUE TRUE
int i:1254; TRUE TRUE
float d:1254.1253999999999; TRUE TRUE
string s:6:”string”; TRUE TRUE
array a:4:{i:0;b:1;i:1;i:1254;i:2;d:1254.1253999999999;i:3;s:6:”string”;} TRUE TRUE
object O:8:”stdClass”:4:{s:4:”bool”;b:1;s:3:”int”;i:1254;s:5:”float”;d:1254.1253999999999;s:6:”string”;s:6:”string”;} TRUE ERROR
DateTime O:8:”DateTime”:3:{s:4:”date”;s:26:”2015-11-09 00:24:03.000000″;s:13:”timezone_type”;i:3;s:8:”timezone”;s:11:”Asia/Taipei”;} TRUE ERROR
custom O:6:”Custom”:3:{s:9:” Custom t”;s:6:”string”;s:5:” * t1″;s:6:”string”;s:3:”int”;i:1254;} TRUE ERROR
ex_class O:8:”ex_class”:6:{s:12:” ex_class tt”;s:6:”string”;s:6:” * tt1″;s:6:”string”;s:4:”tint”;i:1254;s:9:” Custom t”;s:7:”tstring”;s:5:” * t1″;s:7:”tstring”;s:3:”int”;i:1269;} TRUE ERROR

json_encode

形態 序列化值 不含形態驗證 包含形態驗證
bool true TRUE TRUE
int 1254 TRUE TRUE
float 1254.1254 TRUE TRUE
string “string” TRUE TRUE
array [true,1254,1254.1254,”string”] TRUE TRUE
object {“bool”:true,”int”:1254,”float”:1254.1254,”string”:”string”} TRUE ERROR
DateTime {“date”:”2015-11-09 00:24:03.000000″,”timezone_type”:3,”timezone”:”Asia\/Taipei”} ERROR ERROR
custom {“int”:1254} ERROR ERROR
ex_class {“tint”:1254,”int”:1269} ERROR ERROR

var_export

形態 序列化值 不含形態驗證 包含形態驗證
bool true TRUE ERROR
int 1254 TRUE ERROR
float 1254.1253999999999 TRUE ERROR
string ‘string’ ERROR ERROR
array array (
0 => true,
1 => 1254,
2 => 1254.1253999999999,
3 => ‘string’,
)
ERROR ERROR
object stdClass::__set_state(array(
‘bool’ => true,
‘int’ => 1254,
‘float’ => 1254.1253999999999,
‘string’ => ‘string’,
))
ERROR ERROR
DateTime DateTime::__set_state(array(
‘date’ => ‘2015-11-09 00:24:03.000000’,
‘timezone_type’ => 3,
‘timezone’ => ‘Asia/Taipei’,
))
ERROR ERROR
custom Custom::__set_state(array(
‘t’ => ‘string’,
‘t1’ => ‘string’,
‘int’ => 1254,
))
ERROR ERROR
ex_class ex_class::__set_state(array(
‘tt’ => ‘string’,
‘tt1’ => ‘string’,
‘tint’ => 1254,
‘t’ => ‘tstring’,
‘t1’ => ‘tstring’,
‘int’ => 1269,
))
ERROR ERROR

運行的 PHP 程式碼

<?php
$bool = TRUE;
$int = 1254;
$float = 1254.1254;
$string = 'string';
$array = array($bool, $int, $float, $string);
$object = new stdClass();
$object->bool = $bool;
$object->int = $int;
$object->float = $float;
$object->string = $string;
$DateTime = new DateTime();
class Custom {
	private $t;
	protected $t1;
	public $int;
	function __construct($string, $int) {
		$this->t = $string;
		$this->t1 = $string;
		$this->int = $int;
	}
}
class ex_class extends Custom{
	private $tt;
	protected $tt1;
	public $tint;
	function __construct($string, $int) {
		parent::__construct('t' . $string, $int + 15);
		$this->tt = $string;
		$this->tt1 = $string;
		$this->tint = $int;
	}
}
$custom = new Custom('string', 1254);
$ex_class = new ex_class('string', 1254);

$test = compact('bool', 'int', 'float', 'string', 'array', 'object', 'DateTime', 'custom', 'ex_class');

echo('serialize<table class="table">');
echo('<thead><tr><th>形態</th><th>序列化值</th><th>不含形態驗證</th><th>包含形態驗證</th></tr></thead>');
foreach( $test as $key => $item ) {
	$encode = serialize($item);
	echo('<tr><td>' . $key . '</td><td>' . $encode . '</td>');
	$decode = unserialize($encode);
	echo(($decode == $item) ? '<td>TRUE</td>' : '<td>ERROR</td>');
	echo(($decode === $item) ? '<td>TRUE</td>' : '<td>ERROR</td>');
	echo('</tr>'."\n");
}
echo('</table>');

echo('json_encode<table class="table">');
echo('<thead><tr><th>形態</th><th>序列化值</th><th>不含形態驗證</th><th>包含形態驗證</th></tr></thead>');
foreach( $test as $key => $item ) {
	$encode = json_encode($item);
	echo('<tr><td>' . $key . '</td><td>' . $encode . '</td>');
	$decode = json_decode($encode);
	echo(($decode == $item) ? '<td>TRUE</td>' : '<td>ERROR</td>');
	echo(($decode === $item) ? '<td>TRUE</td>' : '<td>ERROR</td>');
	echo('</tr>'."\n");
}
echo('</table>');

echo('var_export<table class="table">');
echo('<thead><tr><th>形態</th><th>序列化值</th><th>不含形態驗證</th><th>包含形態驗證</th></tr></thead>');
foreach( $test as $key => $item ) {
	$encode = var_export($item, TRUE);
	echo('<tr><td>' . $key . '</td><td>' . $encode . '</td>');
	$decode = strval($encode);
	echo(($decode == $item) ? '<td>TRUE</td>' : '<td>ERROR</td>');
	echo(($decode === $item) ? '<td>TRUE</td>' : '<td>ERROR</td>');
	echo('</tr>'."\n");
}
echo('</table>');
?>

json_encode @ 2.8256001191254
json_decode @ 5.67995900737
serialize @ 4.9058028810883
unserialize @ 6.1331700632782

運行的 PHP 程式碼

<?php
set_time_limit(0);
$loop_times = 10000;
$array = array_fill(0, 2000, 'string');
$start = explode(' ', microtime());
for( $i = 0; $i < $loop_times; ++$i ) {
	$data = json_encode($array);
}
$end = explode(' ', microtime());
$time = $end[1] + $end[0] - $start[1] - $start[0];
echo('json_encode @ ' . $time . "\n");

$start = explode(' ', microtime());
for( $i = 0; $i < $loop_times; ++$i ) {
	$array = json_decode($data);
}
$end = explode(' ', microtime());
$time = $end[1] + $end[0] - $start[1] - $start[0];
echo('json_decode @ ' . $time . "\n");

$start = explode(' ', microtime());
for( $i = 0; $i < $loop_times; ++$i ) {
	$data = serialize($array);
}
$end = explode(' ', microtime());
$time = $end[1] + $end[0] - $start[1] - $start[0];
echo('serialize @ ' . $time . "\n");

$start = explode(' ', microtime());
for( $i = 0; $i < $loop_times; ++$i ) {
	$array = unserialize($data);
}
$end = explode(' ', microtime());
$time = $end[1] + $end[0] - $start[1] - $start[0];
echo('unserialize @ ' . $time . "\n");
?>

serialize 很好用但最近遇到一個問題… 字串如果不是純英文字母 (例如有戴帽子的那種字母 @@ 可能會有問題.. 讀不出原本的變數結構.. 不適合存語言字串… 最近這個問題很惱人

你應該是將 serialize 過的字串存入資料庫當中之後再抓出來才使用
你需要先確定 資料庫當中儲存 的是正確的字串。
因為我直接測試 unserialize(serialize(‘Ĝ’)) 是可以正確的出現該字的