存储PHP数组的首选方法(json_encode vs serialize)


Answers

JSON比PHP的序列化格式简单快捷,应该使用JSON, 除非

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

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

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

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

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




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




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

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

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

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

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

结果非常确定,对于这些大到小的数据集,序列化和反序列化都是明显的赢家。 特别是对于我的用例,json_decode和反序列化对于缓存系统来说是最重要的。 反串行化在这里几乎是一个无处不在的赢家。 它通常是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>



似乎序列化是我打算使用的原因有两个:

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

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




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

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




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

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

JSON:

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

原生PHP:

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

Igbinary:

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

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

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

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

另见http://ilia.ws/files/zendcon_2010_hidden_features.pdf - 专门幻灯片14/15/16




如果总结人们在这里说的话,json_decode / encode似乎比序列化/反序列化更快但是如果你做了var_dump,序列化对象的类型就会改变。 如果由于某种原因,你想保持这种类型,请使用序列化!

(尝试例如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,这是不好的,对象信息丢失......所以根据需要来决定,特别是如果它不仅是阵列...




在做出最终决定之前,请注意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
)



真的很棒的话题,在阅读了几个答案之后,我想分享一下我的实验。

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

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

表有14355个条目,包含18列,这些是我读取序列化缓存时的测试和统计数据:

JSON:

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

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

Msgpack

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

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

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

IgBinary

@GingerDog提到https://github.com/phadej/igbinary 。 请注意,我已经设置了igbinary.compact_strings=Off因为我更关心的是阅读表现而不是文件大小。

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

比味精更好。 不过,这个也需要编译。

serialize /反unserialize

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

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

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

我们还可以推断,根据您的需求,您会选择与其他人不同的东西:

  • IgBinary非常好,性能比MsgPack好
  • Msgpack更好地压缩数据(请注意,我没有尝试使用igbinary compact.string选项)。
  • 不想编译? 使用标准。

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

*使用PHPUnit 3.7.31,php 5.5.10进行测试 - 仅使用标准硬盘和旧双核CPU进行解码 - 在10个相同的用例测试中获得平均数字,您的数据可能不同




Related