PHP最好的MD5多维数组方式?


120

生成多维数组的MD5(或任何其他哈希)的最佳方法是什么?

我可以轻松地编写一个循环,遍历数组的每个级别,将每个值连接成一个字符串,然后简单地对字符串执行MD5。

但是,这充其量似乎很麻烦,我想知道是否有一个时髦的函数可以采用多维数组并对它进行哈希处理。

Answers:


260

(底部有可复制n粘贴功能)

如前所述,以下将起作用。

md5(serialize($array));

但是,值得注意的是(具有讽刺意味的)json_encode的执行速度明显更快:

md5(json_encode($array));

实际上,这里的速度提高了两倍,因为(1)仅json_encode的执行速度要比序列化快,并且(2)json_encode产生的字符串较小,因此对md5的处理较少。

编辑:这是支持此主张的证据:

<?php //this is the array I'm using -- it's multidimensional.
$array = unserialize('a:6:{i:0;a:0:{}i:1;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}}i:2;s:5:"hello";i:3;a:2:{i:0;a:0:{}i:1;a:0:{}}i:4;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}}}}i:5;a:5:{i:0;a:0:{}i:1;a:4:{i:0;a:0:{}i:1;a:0:{}i:2;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}i:3;a:6:{i:0;a:0:{}i:1;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}}i:2;s:5:"hello";i:3;a:2:{i:0;a:0:{}i:1;a:0:{}}i:4;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}}}}i:5;a:5:{i:0;a:0:{}i:1;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:3:{i:0;a:0:{}i:1;a:0:{}i:2;a:0:{}}}i:2;s:5:"hello";i:3;a:2:{i:0;a:0:{}i:1;a:0:{}}i:4;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}}}}}}}i:2;s:5:"hello";i:3;a:2:{i:0;a:0:{}i:1;a:0:{}}i:4;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}}}}}}');

//The serialize test
$b4_s = microtime(1);
for ($i=0;$i<10000;$i++) {
    $serial = md5(serialize($array));
}
echo 'serialize() w/ md5() took: '.($sTime = microtime(1)-$b4_s).' sec<br/>';

//The json test
$b4_j = microtime(1);
for ($i=0;$i<10000;$i++) {
    $serial = md5(json_encode($array));
}
echo 'json_encode() w/ md5() took: '.($jTime = microtime(1)-$b4_j).' sec<br/><br/>';
echo 'json_encode is <strong>'.( round(($sTime/$jTime)*100,1) ).'%</strong> faster with a difference of <strong>'.($sTime-$jTime).' seconds</strong>';

JSON_ENCODE始终快250%(2.5倍)以上(通常超过300%)-这并非微不足道。您可能会在此实时脚本中看到测试结果:

现在,需要注意的一件事是array(1,2,3)将产生与array(3,2,1)不同的MD5。 如果这不是您想要的。尝试以下代码:

//Optionally make a copy of the array (if you want to preserve the original order)
$original = $array;

array_multisort($array);
$hash = md5(json_encode($array));

编辑:存在一个关于是否颠倒顺序会产生相同结果的问题。因此,我在这里(正确)完成了此操作:

如您所见,结果完全相同。这是最初由与Drupal相关的人员创建的(更正后的)测试:

出于良好的考虑,以下是您可以复制和粘贴的功能/方法(在5.3.3-1ubuntu9.5中进行了测试):

function array_md5(Array $array) {
    //since we're inside a function (which uses a copied array, not 
    //a referenced array), you shouldn't need to copy the array
    array_multisort($array);
    return md5(json_encode($array));
}

47
大声笑!真?我被“过度”优化否决了?实际上,PHP的序列化要慢得多。我将用证据更新我的答案……
Nathan JB

19
内森在这里所做的事情是有价值的,即使人们看不到它的价值。在我们所处环境之外的某些情况下,这可能是有价值的优化。在某些情况下,但并非在所有情况下,微优化都是一个糟糕的决定
SeanDowney

13
为此,我不是一个进行微优化的人,但是在没有任何额外工作的情况下有记录的性能提升的地方,那为什么不使用它呢。
保险杠盒2012年

2
实际上,它看起来取决于阵列的深度。我碰巧需要一些需要尽可能快地运行的东西,而您的POC显示json_encode()快了300%,当我将代码中的$ array变量更改为用例时,它返回了serialize() w/ md5() took: 0.27773594856262 sec json_encode() w/ md5() took: 0.34809803962708 sec json_encode is (79.8%) faster with a difference of (-0.070362091064453 seconds)(精确计算显然是不正确的)。我的阵列深达2级,因此请记住(与往常一样)您的里程可能会有所不同。
samitny 2012年

3
好的,我不明白为什么内森的答案不是最重要的答案。认真地,使用序列化并用一个庞大的慢速站点来烦扰用户。史诗+1 @ NathanJ.Brauer!
ReSpawN 2014年

168
md5(serialize($array));

13
如果出于某种原因要匹配哈希(指纹),则可能需要考虑对数组进行“排序”或“ ksort”排序,那么还可能需要执行某种清理/清理操作
farinspace

9
序列化比第二个答案慢得多json_encode。让您的服务器愉快并使用json_encode!:)
s3m3n

3
似乎您需要对自己的数组进行基准测试,以确定是否应该使用json_encode或序列化。根据阵列的不同而有所不同。
Ligemer 2014年

我认为这是错误的方式,请在下面查看我的解释。
2014年

1
@joelpittet-不。该drupal链接中的两个示例都有错误。请参阅下面我的答案中的评论。;)例如dl.dropboxusercontent.com/u/4115701/Screenshots/…–
Nathan JB

26

我正在通过回答加入一个非常拥挤的聚会,但是有一个重要的考虑因素,即现有答案都没有解决。的价值json_encode()serialize()既依赖于数组中元素的顺序!

这是在具有相同值但以不同顺序添加的两个数组 上未对数组进行排序和排序的结果(代码在文章底部)

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

因此,我建议对数组进行哈希处理的两种方法是:

// You will need to write your own deep_ksort(), or see
// my example below

md5(   serialize(deep_ksort($array)) );

md5( json_encode(deep_ksort($array)) );

的选择json_encode()还是serialize()应当通过对这些数据的类型测试来确定正在使用。通过我自己对纯文本和数字数据的测试,如果代码没有运行数千次紧密循环,则差异甚至不值得进行基准测试。我个人使用json_encode()这种类型的数据。

这是用于生成上述排序测试的代码:

$a = array();
$a['aa'] = array( 'aaa'=>'AAA', 'bbb'=>'ooo', 'qqq'=>'fff',);
$a['bb'] = array( 'aaa'=>'BBBB', 'iii'=>'dd',);

$b = array();
$b['aa'] = array( 'aaa'=>'AAA', 'qqq'=>'fff', 'bbb'=>'ooo',);
$b['bb'] = array( 'iii'=>'dd', 'aaa'=>'BBBB',);

echo "    serialize()\n";
echo md5(serialize($a))."\n";
echo md5(serialize($b))."\n";

echo "\n    json_encode()\n";
echo md5(json_encode($a))."\n";
echo md5(json_encode($b))."\n";



$a = deep_ksort($a);
$b = deep_ksort($b);

echo "\n    Sorted serialize()\n";
echo md5(serialize($a))."\n";
echo md5(serialize($b))."\n";

echo "\n    Sorted json_encode()\n";
echo md5(json_encode($a))."\n";
echo md5(json_encode($b))."\n";

我的快速deep_ksort()实现适合这种情况,但请在用于自己的项目之前进行检查:

/*
* Sort an array by keys, and additionall sort its array values by keys
*
* Does not try to sort an object, but does iterate its properties to
* sort arrays in properties
*/
function deep_ksort($input)
{
    if ( !is_object($input) && !is_array($input) ) {
        return $input;
    }

    foreach ( $input as $k=>$v ) {
        if ( is_object($v) || is_array($v) ) {
            $input[$k] = deep_ksort($v);
        }
    }

    if ( is_array($input) ) {
        ksort($input);
    }

    // Do not sort objects

    return $input;
}

11

答案在很大程度上取决于数组值的数据类型。对于大字符串,请使用:

md5(serialize($array));

对于短字符串和整数,请使用:

md5(json_encode($array));

4个内置的PHP函数可以将数组转换为字符串: serialize()json_encode()var_export()print_r()

注意: 在处理以字符串为值的关联数组时,json_encode()函数的速度变慢。在这种情况下,请考虑使用serialize()函数。

在键和值中带有md5-哈希(32个字符)的多维数组的测试结果:

Test name       Repeats         Result          Performance     
serialize       10000           0.761195 sec    +0.00%
print_r         10000           1.669689 sec    -119.35%
json_encode     10000           1.712214 sec    -124.94%
var_export      10000           1.735023 sec    -127.93%

数字多维数组的测试结果:

Test name       Repeats         Result          Performance     
json_encode     10000           1.040612 sec    +0.00%
var_export      10000           1.753170 sec    -68.47%
serialize       10000           1.947791 sec    -87.18%
print_r         10000           9.084989 sec    -773.04%

关联数组测试源。数值数组测试源


你能解释什么是字串短线吗?
AL

1
@AL 短字符串 -少于25至30个字符的字符串。大字符串 -全部包含超过25-30个字符。
亚历山大·延查鲁克

7

除了Brock的出色答案(+1)外,任何体面的哈希库都允许您以增量方式更新哈希,因此您应该能够按顺序更新每个字符串,而不必构建一个巨型字符串。

看到: hash_update


值得注意的是,如果要更新的片段很小,则此方法效率不高。不过,这对于大块大文件很有用。
wrygiel 2012年

@wrygiel这是不正确的。对于MD5,压缩总是在64字节的块中完成(无论“大块”的大小如何),并且,如果尚未填充一个块,则在该块被填充之前不会进行任何处理。(完成哈希处理后,最后一个块将被填充为完整块,作为最终处理的一部分。)有关更多背景信息,请阅读Merkle-Damgard构造(MD5,SHA-1和SHA-2均基于此构造))。
克里斯·杰斯特·杨

你是对的。我完全被其他网站上的评论所误导。
wrygiel

@wrygiel这就是为什么遵循“在Internet上找到”的想法进行自己的研究是值得的。;-)这样说,对我来说,最后一条评论很容易写,因为几年前我实际上是从头开始实现MD5的(以练习我的Scheme编程技能),所以我非常了解它的工作原理。
克里斯·杰斯特·杨

这正是我想要的。有时不接受在内存中移动和复制大量数据。因此,与其他答案一样,使用serialize()就性能而言是一个非常糟糕的主意。但是,如果我只想从特定偏移量对字符串的一部分进行哈希处理,则仍然缺少此API。
陈建武

4
md5(serialize($array));

可以使用,但是哈希值会根据数组的顺序而变化(尽管可能没关系)。


3

请注意,serializejson_encode采取不同的,当涉及到数字数组,其中键不从0开始,或关联数组。 json_encode会将此类数组存储为Object,因此json_decode返回Object,其中unserialize将返回具有完全相同键的数组。


3

我认为这可能是一个很好的提示:

Class hasharray {

    public function array_flat($in,$keys=array(),$out=array()){
        foreach($in as $k => $v){
            $keys[] = $k; 
            if(is_array($v)){
                $out = $this->array_flat($v,$keys,$out);
            }else{
                $out[implode("/",$keys)] = $v;
            }
            array_pop($keys);
        }
        return $out;  
    }

    public function array_hash($in){
        $a = $this->array_flat($in);
        ksort($a);
        return md5(json_encode($a));
    }

}

$h = new hasharray;
echo $h->array_hash($multi_dimensional_array);

2

关于的重要说明 serialize()

我不建议将其用作哈希函数的一部分,因为对于以下示例,它可能返回不同的结果。检查以下示例:

简单的例子:

$a = new \stdClass;
$a->test = 'sample';

$b = new \stdClass;
$b->one = $a;
$b->two = clone $a;

产生

"O:8:"stdClass":2:{s:3:"one";O:8:"stdClass":1:{s:4:"test";s:6:"sample";}s:3:"two";O:8:"stdClass":1:{s:4:"test";s:6:"sample";}}"

但是下面的代码:

<?php

$a = new \stdClass;
$a->test = 'sample';

$b = new \stdClass;
$b->one = $a;
$b->two = $a;

输出:

"O:8:"stdClass":2:{s:3:"one";O:8:"stdClass":1:{s:4:"test";s:6:"sample";}s:3:"two";r:2;}"

因此,不用创建第二个对象php,只需创建链接“ r:2;”即可。一审。这绝对是序列化数据的好方法,但是它可能导致哈希函数出现问题。


2
// Convert nested arrays to a simple array
$array = array();
array_walk_recursive($input, function ($a) use (&$array) {
    $array[] = $a;
});

sort($array);

$hash = md5(json_encode($array));

----

These arrays have the same hash:
$arr1 = array(0 => array(1, 2, 3), 1, 2);
$arr2 = array(0 => array(1, 3, 2), 1, 2);

1

有几个答案告诉使用json_code,

但是json_encode在iso-8859-1字符串上无法正常工作,一旦有特殊字符,该字符串就会被裁剪。

我建议使用var_export:

md5(var_export($array, true))

不像序列化那样慢,不像json_encode那样臭虫


不太快,最好的选择是使用md4,var_export也很慢
user956584

0

当前,最受好评的答案md5(serialize($array));不适用于对象。

考虑代码:

 $a = array(new \stdClass());
 $b = array(new \stdClass());

即使数组不同(它们包含不同的对象),使用时它们也具有相同的哈希值md5(serialize($array));。因此,您的哈希是没有用的!

为避免该问题,可以spl_object_hash()在序列化之前用的结果替换对象。如果数组有多个级别,则还应该递归执行。

正如dotancohen所建议的,下面的代码还按键对数组进行排序。

function replaceObjectsWithHashes(array $array)
{
    foreach ($array as &$value) {
        if (is_array($value)) {
            $value = $this->replaceObjectsInArrayWithHashes($value);
        } elseif (is_object($value)) {
            $value = spl_object_hash($value);
        }
    }
    ksort($array);
    return $array;
}

现在您可以使用了md5(serialize(replaceObjectsWithHashes($array)))

(请注意,PHP中的数组是值类型。因此,replaceObjectsWithHashes请勿更改原始数组。)


0

我在上面没有这么轻松地看到解决方案,因此我想提供一个简单的答案。对我来说,直到使用kso​​rt(键排序),我都获得了相同的键:

首先使用Ksort排序,然后在json_encode上执行sha1:

ksort($array)
$hash = sha1(json_encode($array) //be mindful of UTF8

例:

$arr1 = array( 'dealer' => '100', 'direction' => 'ASC', 'dist' => '500', 'limit' => '1', 'zip' => '10601');
ksort($arr1);

$arr2 = array( 'direction' => 'ASC', 'limit' => '1', 'zip' => '10601', 'dealer' => '100', 'dist' => '5000');
ksort($arr2);

var_dump(sha1(json_encode($arr1)));
var_dump(sha1(json_encode($arr2)));

更改后的数组和哈希的输出:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.