先上鸟哥提供的一段代码:
<?php
$size = pow(2, 16);
$startTime = microtime(true);
$array = array();
for($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size)
{
$array[$key] = 0;
}
$endTime = microtime(true);
echo '插入 ', $size, ' 个恶意的元素需要 ', $endTime - $startTime, ' 秒', "\n";
$startTime = microtime(true);
$array = array();
for ($key = 0, $maxKey = $size - 1; $key <= $maxKey; ++$key) {
$array[$key] = 0;
}
$endTime = microtime(true);
echo '插入 ', $size, ' 个普通元素需要 ', $endTime - $startTime, ' 秒', "\n";
在我的机子上跑下来是:
插入 65536 个恶意的元素需要 193.47305703163 秒
插入 65536 个普通元素需要 0.088220834732056 秒
那么恶意元素构造原理到底是什么?
先要知道PHP的数组的一些内幕:
在PHP中,如果键值是数字, 那么Hash的时候就是数字本身, 一般的时候都是,
index & tableMask. 而tableMask是用来保证数字索引不会超出数组可容纳的元素个数值, 也就是数组个数-1.PHP的
Hashtable的大小都是2的指数, 比如如果你存入10个元素的数组, 那么数组实际大小是16, 如果存入20个, 则实际大小为32, 而63个话, 实际大小为64. 当你的存入的元素个数大于了数组目前的最多元素个数的时候, PHP会对这个数组进行扩容, 并且从新Hash.
然后回过去看恶意元素的构造
每个键都是2^16的倍数
从而必然是一路扩容而来的Hashtable的大小的倍数
即键值对Hashtable的大小取模一直是0
从而每次都会分配到同一个桶,
Hashtable蜕化成了链表
这样在每次插入的时候PHP都需要遍历一遍这个链表, 大家可以想象, 第一次插入, 需要遍历0个元素, 第二次是1个, 第三次是3个, 第65536个是65535个, 那么总共就需要65534*65535/2=2147385345次遍历….
以上90%的文字都是转载
用自己习惯的逻辑重整了下
参考:
