【问题标题】:PHP best way to MD5 multi-dimensional array?PHP最好的MD5多维数组方法?
【发布时间】:2011-01-16 06:55:30
【问题描述】:

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

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

然而,这似乎很麻烦,我想知道是否有一个时髦的函数可以接受一个多维数组,并对其进行哈希处理。

【问题讨论】:

    标签: php arrays multidimensional-array hash md5


    【解决方案1】:

    (底部可复制粘贴功能)

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

    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));
    

    编辑:关于颠倒顺序是否会产生相同的结果存在一些问题。所以,我在这里做到了(正确):

    如您所见,结果完全相同。这是(更正)测试originally created by someone related to 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));
    }
    

    【讨论】:

    • 哈哈!真的吗?我投票支持“过度”优化?实际上,PHP 的序列化速度要慢得多。我会用证据更新我的答案...
    • 内森在这里所做的事情是有价值的,即使人们看不到它的价值。在我们上下文之外的某些情况下,这可能是一个有价值的优化。在某些但并非所有情况下,微优化是一个糟糕的决定
    • 我不是为了微优化而进行微优化的人,但是在没有额外工作的情况下记录了性能提升,那么为什么不使用它。
    • 其实,看起来这取决于数组的深度。我碰巧需要一些需要尽可能快地运行的东西,而您的 POC 显示 json_encode() 快了约 300%,当我将代码中的 $array 变量更改为我的用例时,它返回 serialize() w/ md5() took: 0.27773594856262 sec @ 987654334@json_encode is (79.8%) faster with a difference of (-0.070362091064453 seconds)(最近的计算显然不正确)。我的阵列最多有 2 层深,所以请记住(像往常一样)您的里程可能会有所不同。
    • 好吧,我不明白为什么内森的答案不是最佳答案。说真的,使用序列化并用一个非常慢的网站惹恼你的用户。史诗般的 +1 @NathanJ.Brauer!
    【解决方案2】:
    md5(serialize($array));
    

    【讨论】:

    • 如果由于某种原因您想要匹配哈希(指纹),您可能需要考虑对数组“sort”或“ksort”进行排序,另外可能还需要实现某种擦洗/清理
    • 序列化比第二个答案中的 json_encode 慢得多。让您的服务器愉快并使用 json_encode! :)
    • 您似乎需要对自己的数组进行基准测试,以确定是否应该使用 json_encode 或序列化。根据数组不同。
    • 我认为这是错误的方法,请查看我下面的解释。
    • @joelpittet - 不。该 drupal 链接中的两个示例都有错误。请参阅下面我的答案中的 cmets。 ;) 例如dl.dropboxusercontent.com/u/4115701/Screenshots/…
    【解决方案3】:

    我通过回答加入了一个非常拥挤的聚会,但有一个重要的考虑因素是现有的答案都没有解决。 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;
    }
    

    【讨论】:

      【解决方案4】:

      答案高度依赖于数组值的数据类型。 对于大字符串使用:

      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%
      

      关联数组test source。 数值数组test source

      【讨论】:

      • 你能解释一下什么是短字符串吗?
      • @A.L 短字符串 - 包含少于 25-30 个字符的字符串。 大字符串 - 全部包含超过 25-30 个字符。
      【解决方案5】:

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

      见:hash_update

      【讨论】:

      • 值得注意的是,如果您使用小片段进行更新,此方法效率低下;不过,这对大块大文件很有用。
      • @wrygiel 这不是真的。对于 MD5,压缩总是在 64 字节块中完成(不管你的“大块”的大小是多少),如果你还没有填满一个块,在块被填满之前不会进行任何处理。 (当你最终确定哈希时,最后一个块被填充到一个完整的块,作为最终处理的一部分。)有关更多背景信息,请阅读Merkle-Damgard construction(MD5、SHA-1 和 SHA-2 均基于此) .
      • 你是对的。我完全被其他网站上的评论误导了。
      • @wrygiel 这就是为什么在遵循“在 Internet 上找到”的想法时进行自己的研究是值得的。 ;-) 这么说来,最后一条评论对我来说很容易写,因为几年前我实际上是从零开始实现了 MD5(以练习我的 Scheme 编程技能),所以我非常了解它的工作原理。
      • 这正是我想要的。在内存中移动和复制大量数据有时是不可接受的。因此,就像其他答案一样,使用 serialize() 在性能方面是一个非常糟糕的主意。但是如果我只想从某个偏移量散列部分字符串,这个 API 仍然缺失。
      【解决方案6】:
      md5(serialize($array));
      

      会起作用,但散列会根据数组的顺序而改变(尽管这可能无关紧要)。

      【讨论】:

        【解决方案7】:

        请注意,serializejson_encode 对于键不从 0 开始的数值数组或关联数组的行为不同。 json_encode 会将此类数组存储为 Object,因此 json_decode 返回 Object,其中 unserialize 将返回具有完全相同键的数组。

        【讨论】:

          【解决方案8】:

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

          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);
          

          【讨论】:

            【解决方案9】:

            关于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;”到一审。这绝对是序列化数据的好方法,但它可能会导致散列函数出现问题。

            【讨论】:

              【解决方案10】:
              // 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);
              

              【讨论】:

                【解决方案11】:

                有几个答案告诉使用 json_code,

                但是 json_encode 不适用于 iso-8859-1 字符串,一旦有特殊字符,字符串就会被裁剪。

                我建议使用 var_export :

                md5(var_export($array, true))
                

                不像serialize那么慢,不像json_encode那样有bug

                【讨论】:

                • 没那么快,最好的选择是使用 md4,var_export 也很慢
                【解决方案12】:

                目前投票最多的答案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函数不要改变原来的数组。)

                【讨论】:

                • 我完全同意。 ` 序列化(\stdClass())=== 序列化(\stdClass());`
                【解决方案13】:

                我没有这么容易地看到上面的解决方案,所以我想提供一个更简单的答案。对我来说,在使用 ksort(键排序)之前,我得到的是相同的键:

                先用 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"
                

                【讨论】:

                • $sign = sha1(json_encode($data)); 我最终也使用了 sha1,因为它看起来更独特。
                【解决方案14】:

                在某些情况下,使用 http_build_query 将数组转换为字符串可能会更好:

                md5( http_build_query( $array ) );
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-02-19
                  • 2011-11-04
                  • 2015-07-26
                  • 1970-01-01
                  • 2015-02-17
                  • 2017-05-29
                  • 2016-07-24
                  相关资源
                  最近更新 更多