PHP meilleur moyen de tableau multidimensionnel MD5?

120

Quelle est la meilleure façon de générer un MD5 (ou tout autre hachage) d'un tableau multidimensionnel?

Je pourrais facilement écrire une boucle qui traverserait chaque niveau du tableau, concaténant chaque valeur dans une chaîne et exécutant simplement le MD5 sur la chaîne.

Cependant, cela semble au mieux encombrant et je me suis demandé s'il y avait une fonction géniale qui prendrait un tableau multidimensionnel et le hacherait.

Peter John
la source

Réponses:

261

(Fonction copier-coller en bas)

Comme mentionné précédemment, ce qui suit fonctionnera.

md5(serialize($array));

Cependant, il convient de noter que (ironiquement) json_encode fonctionne sensiblement plus rapidement:

md5(json_encode($array));

En fait, l'augmentation de la vitesse est double ici car (1) json_encode seul est plus rapide que sérialiser, et (2) json_encode produit une chaîne plus petite et donc moins à gérer pour md5.

Edit: Voici des preuves à l'appui de cette affirmation:

<?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 est systématiquement plus de 250% (2,5x) plus rapide (souvent plus de 300%) - ce n'est pas une différence anodine. Vous pouvez voir les résultats du test avec ce script en direct ici:

Maintenant, une chose à noter est que array (1,2,3) produira un MD5 différent en tant que array (3,2,1). Si ce n'est PAS ce que vous voulez. Essayez le code suivant:

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

Edit: On s'est demandé si l'inversion de l'ordre produirait les mêmes résultats. Donc, je l'ai fait ( correctement ) ici:

Comme vous pouvez le voir, les résultats sont exactement les mêmes. Voici le test ( corrigé ) créé à l'origine par une personne liée à Drupal :

Et pour faire bonne mesure, voici une fonction / méthode que vous pouvez copier et coller (testée dans 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));
}
Nathan JB
la source
47
LOL! Vraiment? J'ai voté pour l'optimisation "sur"? En réalité, la sérialisation de PHP est nettement plus lente. Je vais mettre à jour ma réponse avec des preuves ...
Nathan JB
19
Ce que Nathan a fait ici est précieux même si on ne peut pas en voir la valeur. Cela peut être une optimisation précieuse dans certaines situations qui sortent de notre contexte. La micro-optimisation est une mauvaise décision dans certaines situations mais pas dans toutes
SeanDowney
13
Je ne suis pas partisan de la micro-optimisation pour le plaisir, mais là où il y a une augmentation des performances documentée sans travail supplémentaire, alors pourquoi ne pas l'utiliser.
bumperbox
2
En fait, il semble que cela dépend de la profondeur du tableau. Il se trouve que j'ai besoin de quelque chose qui doit fonctionner aussi vite que possible et bien que votre POC montre que json_encode () est ~ 300% plus rapide, lorsque j'ai changé la variable $ array de votre code dans mon cas d'utilisation, il est retourné 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)(le calcul précédent est évidemment incorrect). Mon tableau a jusqu'à 2 niveaux de profondeur, alors gardez simplement à l'esprit que (comme d'habitude) votre kilométrage peut varier.
samitny
3
D'accord, je ne vois pas pourquoi la réponse de Nathan n'est pas la meilleure réponse. Sérieusement, utilisez sérialiser et ennuyer vos utilisateurs avec un immense site lent. Épique +1 @ NathanJ.Brauer!
ReSpawN
168
md5(serialize($array));
Brock Batsell
la source
13
si, pour une raison quelconque, vous voulez faire correspondre le hachage (empreinte digitale), vous pouvez envisager de trier le tableau "sort" ou "ksort", implémenter en outre une sorte de nettoyage / nettoyage pourrait également être nécessaire
farinspace
9
La sérialisation est tellement plus lente que json_encode à partir de la deuxième réponse. Faites plaisir à votre serveur et utilisez json_encode! :)
s3m3n
3
Il semble que vous deviez évaluer votre propre tableau afin de déterminer si vous devez utiliser json_encode ou sérialiser. En fonction de la matrice, il diffère.
Ligemer
Je pense que c'est une mauvaise façon, veuillez vérifier mon explication ci-dessous.
TermiT
1
@joelpittet - Non. Les deux exemples de ce lien drupal ont des bogues. Voir les commentaires dans ma réponse ci-dessous. ;) Par exemple dl.dropboxusercontent.com/u/4115701/Screenshots
Nathan JB
26

Je me joins à un groupe très fréquenté en répondant, mais il y a une considération importante qu'aucune des réponses existantes n'aborde. La valeur de json_encode()et les serialize()deux dépendent de l'ordre des éléments dans le tableau!

Voici les résultats de ne pas trier et trier les tableaux, sur deux tableaux avec des valeurs identiques mais ajoutés dans un ordre différent (code en bas de l'article) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Par conséquent, les deux méthodes que je recommanderais pour hacher un tableau seraient:

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

Le choix de json_encode()ou serialize()doit être déterminé en testant le type de données que vous utilisez . D'après mes propres tests sur des données purement textuelles et numériques, si le code n'exécute pas une boucle serrée des milliers de fois, la différence ne vaut même pas la peine d'être comparée. J'utilise personnellement json_encode()pour ce type de données.

Voici le code utilisé pour générer le test de tri ci-dessus:

$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";

Ma mise en œuvre rapide de deep_ksort () convient à ce cas, mais vérifiez-la avant de l'utiliser sur vos propres projets:

/*
* 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
la source
11

La réponse dépend fortement des types de données des valeurs de tableau. Pour les grosses cordes, utilisez:

md5(serialize($array));

Pour les chaînes courtes et les entiers, utilisez:

md5(json_encode($array));

4 fonctions PHP intégrées peuvent transformer un tableau en chaîne: serialize () , json_encode () , var_export () , print_r () .

Remarque: la fonction json_encode () ralentit lors du traitement des tableaux associatifs avec des chaînes comme valeurs. Dans ce cas, envisagez d'utiliser la fonction serialize () .

Résultats des tests pour un tableau multidimensionnel avec des hachages md5 (32 caractères) dans les clés et les valeurs:

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%

Résultat du test pour un tableau multidimensionnel numérique:

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%

Source de test de tableau associatif . Source de test de tableau numérique .

Alexander Yancharuk
la source
Pouvez-vous s'il vous plaît expliquer ce que sont les cordes grandes et courtes ?
AL
1
@AL chaînes courtes - chaînes contenant moins de 25 à 30 caractères. grosses chaînes - contenant toutes plus de 25 à 30 caractères.
Alexander Yancharuk
7

Outre l'excellente réponse de Brock (+1), toute bibliothèque de hachage décente vous permet de mettre à jour le hachage par incréments, vous devriez donc pouvoir mettre à jour chaque chaîne de manière séquentielle, au lieu de devoir créer une chaîne géante.

Voir: hash_update

Chris Jester-Young
la source
il est à noter que cette méthode est inefficace si vous mettez à jour avec de minuscules fragments; c'est bon pour de gros morceaux de gros fichiers.
wrygiel
@wrygiel Ce n'est pas vrai. Pour MD5, la compression se fait toujours en blocs de 64 octets (quelle que soit la taille de vos "gros morceaux"), et, si vous n'avez pas encore rempli un bloc, aucun traitement n'a lieu tant que le bloc n'est pas rempli. (Lorsque vous finalisez le hachage, le dernier bloc est complété jusqu'à un bloc complet, dans le cadre du traitement final.) Pour plus d'informations, lisez la construction Merkle-Damgard (sur laquelle MD5, SHA-1 et SHA-2 sont tous basés ).
Chris Jester-Young
Vous avez raison. J'ai été totalement induit en erreur par un commentaire sur un autre site.
wrygiel
@wrygiel C'est pourquoi il est utile de faire ses propres recherches en suivant une idée "trouvée sur Internet". ;-) En disant cela, ce dernier commentaire a été facile pour moi à écrire, car j'ai en fait implémenté MD5 à partir de zéro il y a quelques années (pour pratiquer mes compétences en programmation Scheme), donc je connais très bien son fonctionnement.
Chris Jester-Young
C'est exactement ce que je veux. Déplacer et copier un gros camion de données en mémoire n'est parfois pas acceptable. Donc, comme d'autres réponses, utiliser serialize () est une très mauvaise idée en termes de performances. Mais cette API est toujours absente si je veux seulement hacher une partie de la chaîne à partir d'un certain décalage.
Jianwu Chen
4
md5(serialize($array));

Cela fonctionnera, mais le hachage changera en fonction de l'ordre du tableau (cela n'a peut-être pas d'importance).

Max Wheeler
la source
3

Notez cela serializeet json_encodeagissez différemment lorsqu'il s'agit de tableaux numériques où les clés ne commencent pas à 0, ou de tableaux associatifs. json_encodestockera ces tableaux sous forme de Object, donc json_decoderenvoie un Object, où unserializeretournera un tableau avec exactement les mêmes clés.

Willem-Jan
la source
3

Je pense que cela pourrait être un bon conseil:

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);
Andrej Pandovich
la source
2

Remarque importante sur serialize()

Je ne recommande pas de l'utiliser dans le cadre de la fonction de hachage car il peut renvoyer des résultats différents pour les exemples suivants. Consultez l'exemple ci-dessous:

Exemple simple:

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

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

Produit

"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";}}"

Mais le code suivant:

<?php

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

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

Production:

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

Donc, au lieu du deuxième objet php, créez simplement le lien "r: 2;" à la première instance. C'est certainement un bon moyen de sérialiser les données, mais cela peut entraîner des problèmes avec votre fonction de hachage.

TermiT
la source
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
la source
1

il y a plusieurs réponses indiquant d'utiliser json_code,

mais json_encode ne fonctionne pas correctement avec la chaîne iso-8859-1, dès qu'il y a un caractère spécial, la chaîne est rognée.

je conseillerais d'utiliser var_export:

md5(var_export($array, true))

pas aussi lent que sérialiser, pas aussi bogué que json_encode

Bruno
la source
Pas si rapide, la meilleure option est d'utiliser md4, var_export est également lent
user956584
0

Actuellement, la réponse la plus votée md5(serialize($array));ne fonctionne pas bien avec les objets.

Considérez le code:

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

Même si les tableaux sont différents (ils contiennent des objets différents), ils ont le même hachage lors de l'utilisation md5(serialize($array));. Donc votre hash est inutile!

Pour éviter ce problème, vous pouvez remplacer les objets par le résultat spl_object_hash()avant la sérialisation. Vous devez également le faire de manière récursive si votre tableau a plusieurs niveaux.

Le code ci-dessous trie également les tableaux par clés, comme l'ont suggéré 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;
}

Vous pouvez maintenant utiliser md5(serialize(replaceObjectsWithHashes($array))).

(Notez que le tableau en PHP est de type valeur. Donc, la replaceObjectsWithHashesfonction NE change PAS le tableau d'origine.)

Damian Polac
la source
0

Je n'ai pas vu la solution si facilement ci-dessus, je voulais donc apporter une réponse plus simple. Pour moi, j'obtenais la même clé jusqu'à ce que j'utilise ksort (tri de clé):

Trié d'abord avec Ksort, puis effectué sha1 sur un json_encode:

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

exemple:

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

Sortie de tableaux et de hachages modifiés:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
Mike Q
la source