PHP - лучший способ многомерного массива MD5?

120

Как лучше всего сгенерировать MD5 (или любой другой хеш) многомерного массива?

Я мог бы легко написать цикл, который проходил бы через каждый уровень массива, объединяя каждое значение в строку и просто выполняя MD5 для строки.

Однако в лучшем случае это кажется обременительным, и мне было интересно, есть ли какая-нибудь забавная функция, которая будет принимать многомерный массив и хэшировать его.

Питер Джон
источник

Ответы:

261

(Функция копирования и вставки внизу)

Как упоминалось ранее, следующее будет работать.

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) создаст другой MD5 как array (3,2,1). Если это НЕ то, что вы хотите. Попробуйте следующий код:

//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 значительно медленнее. Я дополню свой ответ доказательствами ...
Натан Джей Би
19
То, что здесь сделал Натан, ценно, даже если никто не видит в этом ценности. Это может быть ценная оптимизация в некоторых ситуациях, выходящих за рамки нашего контекста. Микрооптимизация - плохое решение в некоторых, но не во всех ситуациях,
Шон Дауни,
13
Я не сторонник микрооптимизации ради этого, но если есть документированное увеличение производительности без дополнительной работы, почему бы не использовать это.
bumperbox 06
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 08
3
Хорошо, я не понимаю, почему ответ Натана не самый лучший. Серьезно, используйте сериализацию и раздражайте своих пользователей очень медленным сайтом. Эпический +1 @ NathanJ.Brauer!
ReSpawN
168
md5(serialize($array));
Брок Батселл
источник
13
если по какой-то причине вы хотите сопоставить хэш (отпечаток пальца), вы можете рассмотреть возможность сортировки массива «sort» или «ksort», дополнительно может потребоваться какая-то очистка / очистка
farinspace
9
Сериализация оооочень медленнее, чем json_encode из второго ответа. Сделайте свой сервер приятным и используйте json_encode! :)
s3m3n 05
3
Похоже, вам нужно протестировать собственный массив, чтобы понять, следует ли использовать json_encode или сериализовать. В зависимости от массива он различается.
Ligemer
Я считаю, что это неправильный путь, пожалуйста, проверьте мое объяснение ниже.
TermiT
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;
}
dotancohen
источник
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

Помимо отличного ответа Брока (+1), любая достойная библиотека хеширования позволяет вам обновлять хэш с приращением, поэтому вы должны иметь возможность обновлять каждую строку последовательно, вместо того, чтобы создавать одну гигантскую строку.

Видеть: hash_update

Крис Джестер-Янг
источник
Стоит отметить, что этот метод неэффективен, если вы обновляете крошечные фрагменты; это хорошо для больших кусков огромных файлов.
wrygiel
@wrygiel Это неправда. Для MD5 сжатие всегда выполняется в 64-байтовых блоках (независимо от размера ваших «больших кусков»), и, если вы еще не заполнили блок, обработка не происходит до тех пор, пока блок не будет заполнен. (Когда вы завершаете хэш, последний блок дополняется до полного, как часть окончательной обработки.) Для получения дополнительной информации прочтите конструкцию Меркла-Дамгарда (на которой основаны MD5, SHA-1 и SHA-2. ).
Крис Джестер-Янг,
Ты прав. Меня полностью ввел в заблуждение комментарий на другом сайте.
wrygiel
@wrygiel Вот почему стоит провести собственное исследование, следуя идее, «найденной в Интернете». ;-) Таким образом, последний комментарий мне было легко написать, потому что я фактически реализовал MD5 с нуля несколько лет назад (чтобы практиковать свои навыки программирования на схемах), поэтому я очень хорошо знаю его работу.
Крис Джестер-Янг,
Это именно то, что я хочу. Перемещение и копирование большого объема данных в памяти иногда неприемлемо. Как и другие ответы, использование serialize () - очень плохая идея с точки зрения производительности. Но этот API все еще отсутствует, если я хочу хешировать только часть String с определенным смещением.
Цзяньву Чен
4
md5(serialize($array));

Будет работать, но хеш будет меняться в зависимости от порядка массива (хотя это может не иметь значения).

Макс Уиллер
источник
3

Обратите внимание на это serializeи json_encodeдействуйте иначе, когда дело касается числовых массивов, где ключи не начинаются с 0, или ассоциативных массивов. json_encodeбудет хранить такие массивы как Object, поэтому json_decodeвозвращает Object, где unserializeвернет массив с точно такими же ключами.

Willem-Jan
источник
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);
ymakux
источник
1

есть несколько ответов, говорящих об использовании json_code,

но json_encode не работает нормально со строкой iso-8859-1, как только есть специальный char, строка обрезается.

я бы посоветовал использовать var_export:

md5(var_export($array, true))

не так медленно, как сериализация, не так глючит, как json_encode

Bruno
источник
Не так быстро, лучший вариант - использовать md4, var_export тоже медленный
user956584
0

В настоящее время ответ, получивший наибольшее количество голосов md5(serialize($array));, не работает с объектами.

Рассмотрим код:

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

Несмотря на то, что массивы разные (они содержат разные объекты), при использовании они имеют одинаковый хэш md5(serialize($array));. Значит, ваш хеш бесполезен!

Чтобы избежать этой проблемы, вы можете заменить объекты на результат spl_object_hash()до сериализации. Вы также должны делать это рекурсивно, если ваш массив имеет несколько уровней.

Код ниже также сортирует массивы по ключам, как предложил дотанкоэн.

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

Я не так легко увидел решение выше, поэтому я хотел предложить более простой ответ. Для меня я получал тот же ключ, пока не использовал ksort (сортировка ключей):

Сначала отсортировано с помощью Ksort, затем выполняется sha1 для json_encode:

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"
Майк Кью
источник