PHP найкращий спосіб для багатовимірного масиву MD5?


120

Який найкращий спосіб генерувати MD5 (або будь-який інший хеш) багатовимірного масиву?

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

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

Відповіді:


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%) - це не банальна різниця. Ви можете побачити результати тесту з цим сценарієм в прямому ефірі тут:

Тепер одне, що слід зазначити, це те, що масив (1,2,3) створить інший MD5 як масив (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 значно повільніше. Я оновлю свою відповідь доказами ...
Nathan JB

19
Те, що Натан зробив тут, є цінним, навіть якщо людина не може бачити його цінності. Це може бути корисною оптимізацією в деяких ситуаціях, що знаходяться поза нашим контекстом.
Мікрооптимізація

13
Я не один для мікрооптимізації заради цього, але там, де зафіксовано документоване підвищення продуктивності без зайвих робіт, то чому б не використати її.
бампербокс

2
Насправді, схоже, це залежить від того, наскільки глибокий масив. Мені трапляється щось, що потрібно запустити якнайшвидше, і поки ваш POC показує, що json_encode () ~ на 300% швидше, коли я змінив змінну масиву $ у вашому коді на мій випадок використання, він повернувся 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 рівнів, тому майте на увазі, що (як зазвичай) ваш пробіг може змінюватися.
самітний

3
Гаразд, я не бачу, чому відповідь Натана - не найкраща відповідь. Серйозно, використовуйте серіалізацію та дратуйте своїх користувачів величезним повільним сайтом. Епічний +1 @ NathanJ.Brauer!
ReSpawN

168
md5(serialize($array));

13
якщо з якоїсь причини ви хочете відповідати хешу (відбитків пальців), ви можете розглянути можливість сортування масиву "сортувати" чи "ksort", можливо також знадобиться додаткова реалізація чищення / очищення
farinspace

9
Серіалізація soooooooo набагато повільніше, ніж json_encode з другої відповіді. Радуйте ваш сервер і використовуйте json_encode! :)
s3m3n

3
Здається, вам потрібно орієнтувати власний масив, щоб зрозуміти, чи слід використовувати json_encode чи серіалізувати. Залежно від масиву він відрізняється.
Лігемер

я вважаю, що це неправильний спосіб, будь ласка, перевірте моє пояснення нижче.
TermiT

1
@joelpittet - Ні. В обох прикладах цього друпальського посилання є помилки. Дивіться коментарі у моїй відповіді нижче. ;) Напр. Dl.dropboxusercontent.com/u/4115701/Screenshots/…
Натан 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 char) у ключах та значеннях:

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-байтових блоках (незалежно від розміру ваших "великих фрагментів"), і якщо ви ще не заповнили блок, жодна обробка не відбувається, поки блок не заповниться. (Коли ви доопрацьовуєте хеш, останній блок залитий до повного блоку, як частина остаточної обробки.) Для отримання додаткової інформації прочитайте конструкцію Merkle-Damgard (на основі якої лежать MD5, SHA-1 і SHA-2 ).
Кріс Єстер-Янг

Ти маєш рацію. Мене повністю ввели в оману коментарі на якомусь іншому сайті.
wrygiel

@wrygiel Ось чому варто робити власне дослідження, дотримуючись ідеї, "знайденої в Інтернеті". ;-) Таким чином, останній коментар мені було легко написати, тому що я реально реалізував MD5 з нуля кілька років тому (щоб практикувати свої навички програмування в схемі), тому я дуже добре знаю його роботу.
Кріс Єстер-Янг

Це саме те, що я хочу. Переміщення та копіювання великої вантажівки даних у пам'ять іноді неприйнятно. Так, як і інші відповіді, використовуючи serialize (), дуже погана ідея з точки зору продуктивності. Але цей API все ще відсутній, якщо я хочу лише зафіксувати частину String з певного зміщення.
Jianwu Chen

4
md5(serialize($array));

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


3

Зауважте, serializeі json_encodeдійте по-різному, якщо мова йде про числових масивах, де ключі не починаються з 0, або асоціативних масивів. json_encodeбуде зберігати такі масиви як an Object, тому json_decodeповертає an 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

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

Спочатку сортується з Ksort, потім виконується sha1 за кодом json_en:

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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.