在PHP中如何序列化一个大型数组?

14

我很好奇,在PHP中序列化是否有大小限制。能否将包含5000个键和值的数组序列化以便存储到缓存中?

我希望能在社交网络网站上缓存用户的好友列表,缓存需要经常更新,但几乎每次加载页面时都需要读取。

在单个服务器设置中,我认为APC比memcache更适合这种情况。

13个回答

27

正如其他人已经回答的那样,仅仅是为了好玩,这里有一个非常快速的基准测试(我敢称之为基准测试吗?);考虑以下代码:

转换后:

正如其他人已经回答的那样,仅仅是为了好玩,这里有一个非常快速的基准测试(我敢称之为 "基准测试" 吗?);考虑以下代码:

$num = 1;

$list = array_fill(0, 5000, str_repeat('1234567890', $num));

$before = microtime(true);
for ($i=0 ; $i<10000 ; $i++) {
    $str = serialize($list);
}
$after = microtime(true);

var_dump($after-$before);
var_dump(memory_get_peak_usage());

我正在PHP 5.2.6上运行(与Ubuntu jaunty捆绑在一起的那个版本)。
是的,这里只有值;没有键;而且这些值相当简单:没有对象,没有子数组,除了字符串之外什么都没有。

对于$num = 1,你会得到:

float(11.8147978783)
int(1702688)

对于$num = 10,你会获得:

float(13.1230671406)
int(2612104)

假设$num = 100,那么你会得到:

float(63.2925770283)
int(11621760)

所以,似乎数组中每个元素越大,花费的时间就越长 (其实这很公平)。但是,对于比原来大100倍的元素,你不需要多花费100倍的时间...


现在,数组有50000个元素,而不是5000个,这意味着代码的这部分已经改变:

$list = array_fill(0, 50000, str_repeat('1234567890', $num));

如果 $num = 1,你将得到:

float(158.236332178)
int(15750752)

考虑到 $num = 10 和 $num = 100 的运行时间,我不会运行此代码...


当然,在实际情况中,您不会执行10000次;因此,让我们只尝试使用for循环的10个迭代。

对于 $num = 1

float(0.206310987473)
int(15750752)

对于 $num = 10

float(0.272629022598)
int(24849832)

对于 $num = 100

float(0.895547151566)
int(114949792)

是的,时间差不多是1秒钟 -- 用了相当多的内存 ^^
(不,这不是生产服务器: 我在这台开发机器上设置了很高的memory_limit ^^)


所以,最终比那些数字要简短一点-- 是的,你可以让数字说任何你想要的话--我不会说有一个 在 PHP 中是“硬编码”的“限制”,但你最终会面临其中之一:

  • max_execution_time(通常在Web服务器上,它从来没有超过30秒)
  • memory_limit(在Web服务器上,它通常不超过32MB)
  • 您的Web服务器负载:当其中一个大型序列化循环正在运行时,它占用了我的1个CPU; 如果您同时有相当多的用户在同一页上,则我让您想象它将会产生什么;-)
  • 您用户的耐心^^

但是,除非您真的正在序列化大数据的长数组,否则我不确定它是否会太重要......
您必须考虑使用该缓存可能帮助您获得的时间/ CPU负载量;-)

然而,最好的方法是通过使用真实数据自己测试;-)


您可能还想看一下 Xdebug 在处理分析时可以做什么:这种情况之一就是它有用的地方!


反复序列化相同数据类型的相同数据并不是真正的基准测试。此外,真正的成本不在于序列化,而在于反序列化。 - Andrew Moore
1
真的(这就是为什么我不确定是否可以称其为基准测试^^),非常真实(因为最常做的是反序列化--否则,没有理由将其放入缓存)。 - Pascal MARTIN
@jasondavis:根据我的回答,使用var_export()将节省您反序列化的成本。 - Andrew Moore
@Andrew:如果他将其存储到APC或memcache中,我不确定var_export是否能解决问题:因为没有文件可包含,而且突然之间,我看不到“好的方法”可以在不评估数据的情况下获取它;你有办法吗?(也许我只是没想到^^) - Pascal MARTIN
另一个真正的限制是你可以在memcache/apc中为一个键分配多少内存 - memcache将一个键限制为一页内存(通常为1MB)。APC可能也是如此。顺便说一下,我确实看到了一个关于include()与unserialize(file_get_contents())的基准测试,令人惊讶的是unserialize获胜了。但这并没有考虑到file_get_contents所使用的内存。 - Justin
显示剩余5条评论

7

serialize()函数的限制仅受可用内存的限制。


5

PHP 没有设置限制。序列化返回序列化结构的字节流表示(字符串),因此您只会得到一个大字符串。


4

没有限制,但请记住序列化和反序列化是有成本的。

反序列化的成本非常高。

一种更经济的缓存数据的方法是使用var_export(),如下所示(自 PHP 5.1.0 起,它适用于对象):

$largeArray = array(1,2,3,'hello'=>'world',4);

file_put_contents('cache.php', "<?php\nreturn ".
                                var_export($largeArray, true).
                                ';');

您可以通过以下方式轻松检索数组:
$largeArray = include('cache.php');

资源通常不可缓存。

不幸的是,如果您的数组中存在循环引用,则需要使用serialize()函数。


这听起来不错,但我不确定对于我的情况而言是否适用。如果使用cache的话,可能会在我的网站上创建100,000个文件和100,000个成员。当我提到cache时,我应该明确是APC还是memcache。 - JasonDavis
你应该在你的原始帖中明确说明。 - Andrew Moore
var_export 需要使用 eval()(这很棘手)。而且与序列化相比,var_export 至少慢 3 倍,并且 var_export 将使用更多的后序列化内存,因为它不是非常紧凑的数据结构。 - Justin

4
唯一的实际限制是您可用的内存,因为序列化涉及在内存中创建一个字符串。

3
如Thinker所建议的:
你可以使用

$string = json_encode($your_array_here);

并对其进行解码

$array = json_decode($your_array_here, true);

这个函数返回一个数组。即使编码后的数组是多层的,它也能正常工作。


我必须补充一点,如果数组中有特殊字符,则使用json进行“序列化”或“字符串化”也会导致问题。像“áéíóúñ”及其大写版本的字符将被utf-8编码,必须特别小心,并且在未编码的json字符串上运行任何类型的字符串验证都是不明智的。特别是如果该验证执行斜杠剥离、引号干扰等操作。这可能会使您的json对象对于php中的**json_decode()**函数无用,并返回错误。 - EffectiX
json_decode() 自动解码这些 UTF-8 编码的字符。只要不破坏编码字符前面的斜杠,就一切都会没问题。 - EffectiX

2

好的...更多数字!(PHP 5.3.0 OSX,没有opcode缓存)

@Pascal的代码在我的机器上,当n=1且迭代10k次时,结果为:

float(18.884856939316)
int(1075900)

我在上述代码中添加了unserialize()函数,如下所示。
$num = 1;

$list = array_fill(0, 5000, str_repeat('1234567890', $num));

$before = microtime(true);
for ($i=0 ; $i<10000 ; $i++) {
    $str = serialize($list);
    $list = unserialize($str);
}
$after = microtime(true);

var_dump($after-$before);
var_dump(memory_get_peak_usage());

产生
float(50.204112052917)
int(1606768) 

我猜多出来的大约 600k 是序列化字符串。
我对 var_export 及其 include/eval 伙伴 $str = var_export($list, true); 感到好奇,它们替代了原本使用的 serialize()。
float(57.064643859863)
int(1066440)

仅仅比这个简单的示例少了一点内存,但时间已经更长了。

在上面使用 eval(“$list =”.$str。”;”)而不是反序列化会产生以下结果

float(126.62566018105)
int(2944144)

执行eval时,很可能存在内存泄漏的问题 :-/。

因此,这些并不是很好的基准测试(我真的应该通过将字符串放在本地变量中来隔离eval/unserialize,但我有点懒),但它们显示了相关趋势。var_export似乎比较慢。


1

不,没有限制,而且这个:

set_time_limit(0);
ini_set('memory_limit ', -1);

unserialize('s:2000000000:"a";');

这就是为什么你应该打开safe.mode或者安装Suhosin这样的扩展,否则它会耗尽你系统中所有的内存。


1
我刚刚遇到一个情况,我认为我已经达到了串行化的上限。
我正在使用mysql的TEXT字段将序列化对象持久化到数据库中。
单字节字符可用字符的限制是65,535,因此,虽然我可以使用PHP对比这更大的对象进行序列化,但由于TEXT字段的限制而无法对它们进行反序列化。

1

我认为比序列化更好的是json_encode函数。它有一个缺点,就是无法区分关联数组和对象,但字符串结果更小,对人类来说更容易阅读、调试和编辑。


4
json_encode可以很好地处理基本数据类型,但是一旦涉及到对象,就会丢失数据的准确性,所以无法使用。 - Andrew Moore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接