首选的方法来存储PHP数组(json_encode与序列化)

我需要在一个平面文件中存储一个多维关联的数据数组,以实现caching目的。 我可能偶尔会遇到需要将其转换为JSON以用于我的Web应用程序,但绝大多数时间我将直接在PHP中使用该数组。

将数组存储为JSON还是作为PHP文本文件中的PHP序列化数组更有效率? 我环顾四周,似乎在最新版本的PHP(5.3)中, json_decode实际上比反unserialize更快。

我目前倾向于将数组存储为JSON,因为如果需要,人们可以更容易地读取它,但是可以在PHP和JavaScript中使用,只需很less的努力,从我读过的内容来看,甚至可能更快的解码(虽然不确定编码)。

有谁知道任何陷阱? 任何人都有良好的基准来显示任一方法的性能优势?

取决于你的优先事项。

如果性能是你绝对的驾驶特性,那么一定要用最快的。 在做出select之前,只要确保你有充分的了解差异

  • 默认情况下, json_encode()将UTF-8字符转换为Unicode转义序列,而serialize()则不转换。 注意:要保持UTF-8字符不变,可以使用PHP 5.4以上版本的JSON_UNESCAPED_UNICODE选项。
  • JSON将不会记忆对象的原始类是什么(它们总是作为stdClass的实例来恢复)。
  • 你不能利用JSON使用__sleep()__wakeup()
  • 只有公共属性使用JSON序列化。 注意:从PHP 5.4开始,可以实现JsonSerializable来改变这种行为。
  • JSON更便携

而且现在可能还有其他一些差别,我想不出来。

一个简单的速度testing来比较两者

 <?php ini_set('display_errors', 1); error_reporting(E_ALL); // Make a big, honkin test array // You may need to adjust this depth to avoid memory limit errors $testArray = fillArray(0, 5); // Time json encoding $start = microtime(true); json_encode($testArray); $jsonTime = microtime(true) - $start; echo "JSON encoded in $jsonTime seconds\n"; // Time serialization $start = microtime(true); serialize($testArray); $serializeTime = microtime(true) - $start; echo "PHP serialized in $serializeTime seconds\n"; // Compare them if ($jsonTime < $serializeTime) { printf("json_encode() was roughly %01.2f%% faster than serialize()\n", ($serializeTime / $jsonTime - 1) * 100); } else if ($serializeTime < $jsonTime ) { printf("serialize() was roughly %01.2f%% faster than json_encode()\n", ($jsonTime / $serializeTime - 1) * 100); } else { echo "Impossible!\n"; } function fillArray( $depth, $max ) { static $seed; if (is_null($seed)) { $seed = array('a', 2, 'c', 4, 'e', 6, 'g', 8, 'i', 10); } if ($depth < $max) { $node = array(); foreach ($seed as $key) { $node[$key] = fillArray($depth + 1, $max); } return $node; } return 'empty'; } 

JSON比PHP的序列化格式更简单快捷, 除非

  • 您正在存储深度嵌套的数组: json_decode() :“如果JSON编码数据比127个元素更深,则此函数将返回false。
  • 你存储的对象需要被反序列化为正确的类
  • 您正在与不支持json_decode的旧版PHP进行交互

我写了一篇关于这个主题的博文 :“ caching一个大数组:JSON,序列化或var_export? ”。 在这篇文章中,显示了serialize是小型到大型arrays的最佳select。 对于非常大的数组(> 70MB),JSON是更好的select。

您可能还对https://github.com/phadej/igbinary感兴趣,它为PHP提供了不同的序列化“引擎”。;

我在64位平台上使用PHP 5.3.5的随机/任意“性能”数据显示:

JSON:

  • JSON编码在2.180496931076秒
  • JSON在9.8368630409241秒内解码
  • 序列化的“string”大小:13993

本机PHP:

  • PHP在2.9125759601593秒内序列化
  • PHP反序列化在6.4348418712616秒
  • 序列化的“string”大小:20769

Igbinary:

  • WIN igbinary在1.6099879741669秒内序列化
  • WIN igbinrary在4.7737920284271秒内反序列化
  • WIN序列化的“string”大小:4467

所以,igbinary_serialize()和igbinary_unserialize()会更快,并且使用更less的磁盘空间。

我使用上面的fillArray(0,3)代码,但使数组键更长的string。

igbinary可以存储与PHP本地序列化相同的数据types(因此不存在对象等问题),如果您愿意,可以告诉PHP5.3使用它来进行会话处理。

另见http://ilia.ws/files/zendcon_2010_hidden_​​features.pdf – 具体幻灯片14/15/16

Y只是testing序列化和JSON编码和解码,再加上它将采取string存储的大小。

 JSON encoded in 0.067085981369 seconds. Size (1277772) PHP serialized in 0.12110209465 seconds. Size (1955548) JSON decode in 0.22470498085 seconds PHP serialized in 0.211947917938 seconds json_encode() was roughly 80.52% faster than serialize() unserialize() was roughly 6.02% faster than json_decode() JSON string was roughly 53.04% smaller than Serialized string 

我们可以得出结论,JSON编码速度更快,结果string更小,但反序列化对string解码更快。

如果您正在caching最终希望在稍后时间“包含”的信息,则可能需要尝试使用var_export 。 这样你只能在“序列化”而不是在“反序列化”中击中。

我扩大了testing,以包括非序列化性能。 这是我得到的数字。

 Serialize JSON encoded in 2.5738489627838 seconds PHP serialized in 5.2861361503601 seconds Serialize: json_encode() was roughly 105.38% faster than serialize() Unserialize JSON decode in 10.915472984314 seconds PHP unserialized in 7.6223039627075 seconds Unserialize: unserialize() was roughly 43.20% faster than json_decode() 

所以json似乎编码更快,但解码速度慢。 所以它可能取决于你的应用程序和你期望做的最多。

看起来像序列化是我打算使用的原因有两个:

  • 有人指出,反序列化比json_decode快,读取的情况听起来比“写入”的情况更可能。

  • 在使用无效的UTF-8字符的string时,我遇到了json_encode的问题。 当发生这种情况时,string最终变空,导致信息丢失。

我已经在一个相当复杂的,温和嵌套的多哈希上testing了这个非常全面的数据(string,NULL,整数)和序列化/反序列化比json_encode / json_decode快得多。

json在我的testing中唯一的优势是它的尺寸更小。

这些都是在PHP 5.3.3下完成的,让我知道你是否想要更多的细节。

这里是testing结果,然后是生成它们的代码。 我无法提供testing数据,因为它会泄露我不能在野外放弃的信息。

 JSON encoded in 2.23700618744 seconds PHP serialized in 1.3434419632 seconds JSON decoded in 4.0405561924 seconds PHP unserialized in 1.39393305779 seconds serialized size : 14549 json_encode size : 11520 serialize() was roughly 66.51% faster than json_encode() unserialize() was roughly 189.87% faster than json_decode() json_encode() string was roughly 26.29% smaller than serialize() // Time json encoding $start = microtime( true ); for($i = 0; $i < 10000; $i++) { json_encode( $test ); } $jsonTime = microtime( true ) - $start; echo "JSON encoded in $jsonTime seconds<br>"; // Time serialization $start = microtime( true ); for($i = 0; $i < 10000; $i++) { serialize( $test ); } $serializeTime = microtime( true ) - $start; echo "PHP serialized in $serializeTime seconds<br>"; // Time json decoding $test2 = json_encode( $test ); $start = microtime( true ); for($i = 0; $i < 10000; $i++) { json_decode( $test2 ); } $jsonDecodeTime = microtime( true ) - $start; echo "JSON decoded in $jsonDecodeTime seconds<br>"; // Time deserialization $test2 = serialize( $test ); $start = microtime( true ); for($i = 0; $i < 10000; $i++) { unserialize( $test2 ); } $unserializeTime = microtime( true ) - $start; echo "PHP unserialized in $unserializeTime seconds<br>"; $jsonSize = strlen(json_encode( $test )); $phpSize = strlen(serialize( $test )); echo "<p>serialized size : " . strlen(serialize( $test )) . "<br>"; echo "json_encode size : " . strlen(json_encode( $test )) . "<br></p>"; // Compare them if ( $jsonTime < $serializeTime ) { echo "json_encode() was roughly " . number_format( ($serializeTime / $jsonTime - 1 ) * 100, 2 ) . "% faster than serialize()"; } else if ( $serializeTime < $jsonTime ) { echo "serialize() was roughly " . number_format( ($jsonTime / $serializeTime - 1 ) * 100, 2 ) . "% faster than json_encode()"; } else { echo 'Unpossible!'; } echo '<BR>'; // Compare them if ( $jsonDecodeTime < $unserializeTime ) { echo "json_decode() was roughly " . number_format( ($unserializeTime / $jsonDecodeTime - 1 ) * 100, 2 ) . "% faster than unserialize()"; } else if ( $unserializeTime < $jsonDecodeTime ) { echo "unserialize() was roughly " . number_format( ($jsonDecodeTime / $unserializeTime - 1 ) * 100, 2 ) . "% faster than json_decode()"; } else { echo 'Unpossible!'; } echo '<BR>'; // Compare them if ( $jsonSize < $phpSize ) { echo "json_encode() string was roughly " . number_format( ($phpSize / $jsonSize - 1 ) * 100, 2 ) . "% smaller than serialize()"; } else if ( $phpSize < $jsonSize ) { echo "serialize() string was roughly " . number_format( ($jsonSize / $phpSize - 1 ) * 100, 2 ) . "% smaller than json_encode()"; } else { echo 'Unpossible!'; } 

真的很好的话题,并阅读了几个答案后,我想分享我的实验在这个问题上。

我有一个用例,几乎每次我和数据库交谈时都需要查询一些“巨大”的表(不要问为什么,只是一个事实)。 数据库caching系统是不合适的,因为它不会caching不同的请求,所以我关于PHPcaching系统。

我尝试了apcu但它不符合需要,在这种情况下,内存不够可靠。 下一步是caching到序列化文件。

表有14355条18列,这些是我的testing和阅读序列化caching统计:

JSON:

正如你所说的, json_encode / json_decode的主要不便之处在于它把所有东西都转换成StdClass实例(或者Object)。 如果你需要循环,把它转换成数组就是你可能做的,而且是的,这会增加转换时间

平均时间:780.2毫秒; 内存使用:41.5MB; caching文件大小:3.8MB

Msgpack

@hutch提到msgpack 。 漂亮的网站。 让我们试试看吧?

平均时间:497毫秒; 内存使用:32MB; caching文件大小:2.8MB

这更好,但需要一个新的扩展; 编译有时候害怕的人

IgBinary

@GingerDog提到igbinary 。 请注意,我已经设置了igbinary.compact_strings=Off因为我更关心的是读取性能而不是文件大小。

平均时间:411.4毫秒; 内存使用:36.75MB; caching文件大小:3.3MB

比信息包好。 不过,这个也需要编译。

serialize /反unserialize

平均时间:477.2毫秒; 内存使用:36.25MB; caching文件大小:5.9MB

比JSON更好的性能,数组越大, json_decode越慢,但是你已经是新的了。

这些外部扩展正在缩小文件大小,在纸上看起来很棒。 数字不说谎*。 如果获得与使用标准PHP函数几乎相同的结果,编译扩展的意义何在?

我们也可以推断,根据你的需要,你会select不同于别人的东西:

  • IgBinary是非常好的,比MsgPackperformance更好
  • Msgpack更好地压缩你的数据(注意,我没有试过igbinary compact.string选项)。
  • 不想编译? 使用标准。

就是这样,另一个序列化方法比较来帮助你select一个!

*testingPHPUnit 3.7.31,PHP 5.5.10 – 只用一个标准的硬盘和旧的双核CPU解码 – 在10个相同的用例testing平均数字,你的统计可能是不同的

我也做了一个小基准。 我的结果是一样的。 但是我需要解码性能。 在我注意到的地方,就像上面提到的几个人一样,反unserializejson_decode快。 unserialize大约需要json_decode时间的60-70%。 所以结论相当简单:当你需要编码性能的时候,使用json_encode ,当你在解码时需要性能的时候,使用反unserialize 。 因为你不能合并这两个函数,所以你需要做一个select,在那里你需要更多的性能。

我的伪基准:

  • 用数个随机键和值定义数组$ arr
  • 对于x <100; X ++; 序列化和json_encode $ arr的array_rand
  • 对于y <1000; ÿ++; json_decode json编码的string – 计算时间
  • 对于y <1000; ÿ++; 反序列化string – 计算时间
  • 回应更快的结果

在avarage上:反序列化赢得了96倍于json_decode的4倍。 在2.5ms内大约1.5ms的平均值。

在做出最终决定之前,请注意JSON格式对于关联数组是不安全的 – json_decode()会将它们作为对象返回:

 $config = array( 'Frodo' => 'hobbit', 'Gimli' => 'dwarf', 'Gandalf' => 'wizard', ); print_r($config); print_r(json_decode(json_encode($config))); 

输出是:

 Array ( [Frodo] => hobbit [Gimli] => dwarf [Gandalf] => wizard ) stdClass Object ( [Frodo] => hobbit [Gimli] => dwarf [Gandalf] => wizard ) 

只是一个fyi – 如果你想序列化你的数据,像JSON一样容易阅读和理解,但更多的压缩和更高的性能,你应该检查出messagepack。

看看这里的结果(对于将PHP代码放在JS代码框中的黑客抱歉):

http://jsfiddle.net/newms87/h3b0a0ha/embedded/result/

结果:在不同大小的数组中, serialize()unserialize()在PHP 5.4中都显着更快。

我做了一个真实世界的数据testing脚本比较json_encode与序列化和json_decode与反序列化。 该testing是在生产电子商务网站的caching系统上运行的。 它只需要获取caching中的数据,然后testing所有数据的编码/解码(或序列化/反序列化)的时间,并将其放在一个易于查看的表格中。

我在PHP 5.4共享托pipe服务器上运行这个。

结果是非常确定的,对于这些大到小的数据集,序列化和反序列化是明显的赢家。 特别是对于我的用例,json_decode和unserialize对于caching系统来说是最重要的。 反序列化在这里几乎是一个无处不在的赢家。 它通常是json_decode的2到4倍(有时是6或7倍)。

注意@ peter-bailey的结果是有趣的。

以下是用于生成结果的PHP代码:

 <?php ini_set('display_errors', 1); error_reporting(E_ALL); function _count_depth($array) { $count = 0; $max_depth = 0; foreach ($array as $a) { if (is_array($a)) { list($cnt, $depth) = _count_depth($a); $count += $cnt; $max_depth = max($max_depth, $depth); } else { $count++; } } return array( $count, $max_depth + 1, ); } function run_test($file) { $memory = memory_get_usage(); $test_array = unserialize(file_get_contents($file)); $memory = round((memory_get_usage() - $memory) / 1024, 2); if (empty($test_array) || !is_array($test_array)) { return; } list($count, $depth) = _count_depth($test_array); //JSON encode test $start = microtime(true); $json_encoded = json_encode($test_array); $json_encode_time = microtime(true) - $start; //JSON decode test $start = microtime(true); json_decode($json_encoded); $json_decode_time = microtime(true) - $start; //serialize test $start = microtime(true); $serialized = serialize($test_array); $serialize_time = microtime(true) - $start; //unserialize test $start = microtime(true); unserialize($serialized); $unserialize_time = microtime(true) - $start; return array( 'Name' => basename($file), 'json_encode() Time (s)' => $json_encode_time, 'json_decode() Time (s)' => $json_decode_time, 'serialize() Time (s)' => $serialize_time, 'unserialize() Time (s)' => $unserialize_time, 'Elements' => $count, 'Memory (KB)' => $memory, 'Max Depth' => $depth, 'json_encode() Win' => ($json_encode_time > 0 && $json_encode_time < $serialize_time) ? number_format(($serialize_time / $json_encode_time - 1) * 100, 2) : '', 'serialize() Win' => ($serialize_time > 0 && $serialize_time < $json_encode_time) ? number_format(($json_encode_time / $serialize_time - 1) * 100, 2) : '', 'json_decode() Win' => ($json_decode_time > 0 && $json_decode_time < $serialize_time) ? number_format(($serialize_time / $json_decode_time - 1) * 100, 2) : '', 'unserialize() Win' => ($unserialize_time > 0 && $unserialize_time < $json_decode_time) ? number_format(($json_decode_time / $unserialize_time - 1) * 100, 2) : '', ); } $files = glob(dirname(__FILE__) . '/system/cache/*'); $data = array(); foreach ($files as $file) { if (is_file($file)) { $result = run_test($file); if ($result) { $data[] = $result; } } } uasort($data, function ($a, $b) { return $a['Memory (KB)'] < $b['Memory (KB)']; }); $fields = array_keys($data[0]); ?> <table> <thead> <tr> <?php foreach ($fields as $f) { ?> <td style="text-align: center; border:1px solid black;padding: 4px 8px;font-weight:bold;font-size:1.1em"><?= $f; ?></td> <?php } ?> </tr> </thead> <tbody> <?php foreach ($data as $d) { ?> <tr> <?php foreach ($d as $key => $value) { ?> <?php $is_win = strpos($key, 'Win'); ?> <?php $color = ($is_win && $value) ? 'color: green;font-weight:bold;' : ''; ?> <td style="text-align: center; vertical-align: middle; padding: 3px 6px; border: 1px solid gray; <?= $color; ?>"><?= $value . (($is_win && $value) ? '%' : ''); ?></td> <?php } ?> </tr> <?php } ?> </tbody> </table> 

如果要备份数据并在另一台计算机上或通过FTP恢复,JSON会更好。

例如,如果您将数据存储在Windows服务器上,则通过FTP下载并在Linux上进行恢复,但由于charachter重新编码,无法再工作,因为serialize存储string的长度和Unicode > UTF-8转码一些1字节的字符可能变成2个字节,使algorithm崩溃。

THX – 对于这个基准代码:

我用于configuration的数组结果如下:JSON编码在0.0031511783599854秒
PHP序列化在0.0037961006164551秒
json_encode()比在0.0070841312408447秒内编码的serialize() JSON快大约20.47%
PHP序列化在0.0035839080810547秒
unserialize()json_encode()快大约97.66%

所以 – testing你自己的数据。

首先,我改变脚本做更多的基准testing(也做1000次而不是1次):

 <?php ini_set('display_errors', 1); error_reporting(E_ALL); // Make a big, honkin test array // You may need to adjust this depth to avoid memory limit errors $testArray = fillArray(0, 5); $totalJsonTime = 0; $totalSerializeTime = 0; $totalJsonWins = 0; for ($i = 0; $i < 1000; $i++) { // Time json encoding $start = microtime(true); $json = json_encode($testArray); $jsonTime = microtime(true) - $start; $totalJsonTime += $jsonTime; // Time serialization $start = microtime(true); $serial = serialize($testArray); $serializeTime = microtime(true) - $start; $totalSerializeTime += $serializeTime; if ($jsonTime < $serializeTime) { $totalJsonWins++; } } $totalSerializeWins = 1000 - $totalJsonWins; // Compare them if ($totalJsonTime < $totalSerializeTime) { printf("json_encode() (wins: $totalJsonWins) was roughly %01.2f%% faster than serialize()\n", ($totalSerializeTime / $totalJsonTime - 1) * 100); } else { printf("serialize() (wins: $totalSerializeWins) was roughly %01.2f%% faster than json_encode()\n", ($totalJsonTime / $totalSerializeTime - 1) * 100); } $totalJsonTime = 0; $totalJson2Time = 0; $totalSerializeTime = 0; $totalJsonWins = 0; for ($i = 0; $i < 1000; $i++) { // Time json decoding $start = microtime(true); $orig = json_decode($json, true); $jsonTime = microtime(true) - $start; $totalJsonTime += $jsonTime; $start = microtime(true); $origObj = json_decode($json); $jsonTime2 = microtime(true) - $start; $totalJson2Time += $jsonTime2; // Time serialization $start = microtime(true); $unserial = unserialize($serial); $serializeTime = microtime(true) - $start; $totalSerializeTime += $serializeTime; if ($jsonTime < $serializeTime) { $totalJsonWins++; } } $totalSerializeWins = 1000 - $totalJsonWins; // Compare them if ($totalJsonTime < $totalSerializeTime) { printf("json_decode() was roughly %01.2f%% faster than unserialize()\n", ($totalSerializeTime / $totalJsonTime - 1) * 100); } else { printf("unserialize() (wins: $totalSerializeWins) was roughly %01.2f%% faster than json_decode()\n", ($totalJsonTime / $totalSerializeTime - 1) * 100); } // Compare them if ($totalJson2Time < $totalSerializeTime) { printf("json_decode() was roughly %01.2f%% faster than unserialize()\n", ($totalSerializeTime / $totalJson2Time - 1) * 100); } else { printf("unserialize() (wins: $totalSerializeWins) was roughly %01.2f%% faster than array json_decode()\n", ($totalJson2Time / $totalSerializeTime - 1) * 100); } function fillArray( $depth, $max ) { static $seed; if (is_null($seed)) { $seed = array('a', 2, 'c', 4, 'e', 6, 'g', 8, 'i', 10); } if ($depth < $max) { $node = array(); foreach ($seed as $key) { $node[$key] = fillArray($depth + 1, $max); } return $node; } return 'empty'; } 

我使用PHP 7的这个版本:

PHP 7.0.14(cli)(制作:Jan 18 2017 19:13:23)(NTS)版权所有(c)1997-2016 PHP集团Zend引擎v3.0.0,版权所有(c)1998-2016 Zend技术与Zend OPcache版本(c)1999-2016,由Zend Technologies

我的结果是:

unserialize()(胜率:987)比json_decode()快大约33.26% ()

明显 ,serialize / unserialize是最快的方法,而json_encode / decode是最便携的。

如果考虑一种情况,即读写序列化数据的次数要比发送或接收非PHP系统时多10倍或更多,那么最好使用序列化/反序列化,并在序列化之前使用json_encode或json_decode在时间方面。

如果总结人们在这里说,json_decode /编码似乎比序列化/反序列化更快但是如果你做var_dump序列化对象的types改变了。 如果由于某种原因,你想保持这种types,去序列化!

(尝试例如stdClass vs数组)

序列化/反序列化:

 Array cache: array (size=2) 'a' => string '1' (length=1) 'b' => int 2 Object cache: object(stdClass)[8] public 'field1' => int 123 This cache: object(Controller\Test)[8] protected 'view' => 

json编码/解码

 Array cache: object(stdClass)[7] public 'a' => string '1' (length=1) public 'b' => int 2 Object cache: object(stdClass)[8] public 'field1' => int 123 This cache: object(stdClass)[8] 

正如你所看到的json_encode / decode将所有转换为stdClass,这是不好的,对象信息丢失…所以决定根据需要,特别是如果它不仅是arrays…