Il modo migliore di PHP per array multidimensionali MD5?


120

Qual è il modo migliore per generare un MD5 (o qualsiasi altro hash) di un array multidimensionale?

Potrei facilmente scrivere un ciclo che attraversi ogni livello dell'array, concatenando ogni valore in una stringa e semplicemente eseguendo l'MD5 sulla stringa.

Tuttavia, questo sembra al massimo ingombrante e mi chiedevo se ci fosse una funzione funky che avrebbe preso un array multidimensionale e lo avrebbe hashing.

Risposte:


260

(Funzione copia e incolla in basso)

Come accennato in precedenza, funzionerà quanto segue.

md5(serialize($array));

Tuttavia, vale la pena notare che (ironicamente) json_encode si comporta notevolmente più velocemente:

md5(json_encode($array));

In effetti, l'aumento di velocità è duplice qui poiché (1) json_encode da solo è più veloce di serializzare, e (2) json_encode produce una stringa più piccola e quindi meno da gestire per md5.

Modifica: ecco le prove a sostegno di questa affermazione:

<?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 è costantemente oltre il 250% (2,5 volte) più veloce (spesso oltre il 300%): questa non è una differenza banale. Puoi vedere i risultati del test con questo live script qui:

Ora, una cosa da notare è che array (1,2,3) produrrà un MD5 diverso come array (3,2,1). Se questo NON è quello che vuoi. Prova il codice seguente:

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

Modifica: ci sono stati alcuni dubbi sul fatto che l'inversione dell'ordine avrebbe prodotto gli stessi risultati. Quindi, l'ho fatto ( correttamente ) qui:

Come puoi vedere, i risultati sono esattamente gli stessi. Ecco il test ( corretto ) originariamente creato da qualcuno legato a Drupal :

E per buona misura, ecco una funzione / metodo che puoi copiare e incollare (testato in 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
LOL! Veramente? Sono stato votato per l'ottimizzazione "eccessiva"? In realtà, la serializzazione di PHP è notevolmente più lenta. Aggiornerò la mia risposta con le prove ...
Nathan JB

19
Ciò che Nathan ha fatto qui è prezioso anche se non se ne può vedere il valore. Può essere una preziosa ottimizzazione in alcune situazioni che sono al di fuori del nostro contesto. La micro ottimizzazione è una decisione sbagliata in alcune ma non tutte le situazioni
SeanDowney

13
Non sono uno per la microottimizzazione per il gusto di farlo, ma dove c'è un aumento delle prestazioni documentato senza lavoro extra, perché non usarlo.
bumperbox

2
In realtà, sembra che dipenda da quanto è profondo l'array. Mi capita di aver bisogno di qualcosa che deve funzionare il più velocemente possibile e mentre il tuo POC mostra che json_encode () è ~ 300% più veloce, quando ho cambiato la variabile $ array nel tuo codice nel mio caso d'uso, è tornato 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)(il calcolo precedente è ovviamente errato). Il mio array è profondo fino a 2 livelli, quindi tieni presente che (come al solito) il tuo chilometraggio può variare.
samitny

3
Ok, non vedo perché la risposta di Nathan non è la risposta migliore. Seriamente, usa serializza e infastidisci i tuoi utenti con un sito immenso e lento. Epico +1 @ NathanJ.Brauer!
ReSpawN

168
md5(serialize($array));

13
se per qualche motivo vuoi far corrispondere l'hash (impronta digitale) potresti prendere in considerazione l'ordinamento dell'array "sort" o "ksort", inoltre potrebbe essere necessaria anche l'implementazione di una sorta di scrubbing / cleaning
farinspace

9
Serialize è tremendamente molto più lento di json_encode dalla seconda risposta. Fai un piacere al tuo server e usa json_encode! :)
s3m3n

3
Sembra che tu abbia bisogno di confrontare il tuo array per capire se dovresti usare json_encode o serializzare. A seconda dell'array è diverso.
Ligemer

credo che sia un modo sbagliato, per favore controlla la mia spiegazione di seguito.
Termine

1
@joelpittet - No. Entrambi gli esempi in quel collegamento drupal hanno bug. Vedi i commenti nella mia risposta qui sotto. ;) Ad esempio dl.dropboxusercontent.com/u/4115701/Screenshots/…
Nathan JB

26

Mi unisco a una festa molto affollata rispondendo, ma c'è una considerazione importante che nessuna delle risposte esistenti affronta. Il valore di json_encode()ed serialize()entrambi dipendono dall'ordine degli elementi nella matrice!

Di seguito sono riportati i risultati di non ordinare e ordinare gli array, su due array con valori identici ma aggiunti in un ordine diverso (codice in fondo al post) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Pertanto, i due metodi che consiglierei per eseguire l' hashing di un array sarebbero:

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

La scelta di json_encode()o serialize()dovrebbe essere determinata testando il tipo di dati che stai utilizzando . Secondo i miei test su dati puramente testuali e numerici, se il codice non esegue un ciclo stretto migliaia di volte, la differenza non vale nemmeno la pena di essere confrontata. Io personalmente uso json_encode()per quel tipo di dati.

Ecco il codice utilizzato per generare il test di ordinamento sopra:

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

La mia rapida implementazione deep_ksort (), si adatta a questo caso ma controllala prima di utilizzarla sui tuoi progetti:

/*
* 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

La risposta dipende molto dai tipi di dati dei valori degli array. Per grandi archi utilizzare:

md5(serialize($array));

Per stringhe brevi e numeri interi utilizzare:

md5(json_encode($array));

4 funzioni PHP integrate possono trasformare un array in una stringa: serialize () , json_encode () , var_export () , print_r () .

Avviso: la funzione json_encode () rallenta durante l'elaborazione di array associativi con stringhe come valori. In questo caso considera di utilizzare la funzione serialize () .

Risultati del test per array multidimensionali con hash md5 (32 caratteri) in chiavi e valori:

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%

Risultato del test per array numerico multidimensionale:

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%

Origine test array associativo . Origine test matrice numerica .


Puoi spiegare cosa sono le corde grandi e quelle corte ?
AL

1
@AL short strings - stringhe che contengono meno di 25-30 caratteri. stringhe grandi - tutte contenenti più di 25-30 caratteri.
Alexander Yancharuk

7

A parte l'eccellente risposta di Brock (+1), qualsiasi libreria di hashing decente ti consente di aggiornare l'hash in incrementi, quindi dovresti essere in grado di aggiornare con ogni stringa in sequenza, invece di dover costruire una stringa gigante.

Vedere: hash_update


vale la pena notare che questo metodo è inefficiente se stai aggiornando con piccoli frammenti; è buono per grandi blocchi di file enormi però.
wrygiel

@wrygiel Questo non è vero. Per MD5, la compressione viene sempre eseguita in blocchi da 64 byte (non importa quale sia la dimensione dei tuoi "grandi blocchi") e, se non hai ancora riempito un blocco, non avviene alcuna elaborazione fino a quando il blocco non è riempito. (Quando si finalizza l'hash, l'ultimo blocco viene riempito fino a un blocco completo, come parte dell'elaborazione finale.) Per ulteriori informazioni, leggere la costruzione di Merkle-Damgard (su cui MD5, SHA-1 e SHA-2 si basano ).
Chris Jester-Young,

Hai ragione. Sono stato totalmente fuorviato da un commento su qualche altro sito.
wrygiel

@wrygiel Ecco perché vale la pena fare le proprie ricerche quando si segue un'idea "trovata su Internet". ;-) Detto questo, quell'ultimo commento è stato facile per me da scrivere, perché in realtà ho implementato MD5 da zero alcuni anni fa (per esercitare le mie capacità di programmazione Scheme), quindi conosco molto bene il suo funzionamento.
Chris Jester-Young,

Questo è esattamente quello che voglio. Spostare e copiare grandi quantità di dati in memoria a volte non è accettabile. Quindi, come altre risposte, usare serialize () è una pessima idea in termini di prestazioni. Ma questa API manca ancora se voglio solo eseguire l'hashing di parte della stringa da un certo offset.
Jianwu Chen

4
md5(serialize($array));

Funzionerà, ma l'hash cambierà a seconda dell'ordine dell'array (potrebbe non essere importante).


3

Nota che serializee json_encodeagisci in modo diverso quando si tratta di array numerici in cui le chiavi non iniziano da 0, o array associativi. json_encodememorizzerà tali array come un Object, quindi json_decoderestituisce an Object, dove unserializerestituirà un array con esattamente le stesse chiavi.


3

Penso che questo potrebbe essere un buon consiglio:

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

Nota importante su serialize()

Non consiglio di usarlo come parte della funzione di hashing perché può restituire risultati diversi per i seguenti esempi. Controlla l'esempio di seguito:

Esempio semplice:

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

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

produce

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

Ma il codice seguente:

<?php

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

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

Produzione:

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

Quindi invece del secondo oggetto php crea semplicemente il link "r: 2;" al primo grado. È sicuramente un modo buono e corretto per serializzare i dati, ma può portare a problemi con la funzione di hashing.


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

ci sono diverse risposte che dicono di usare json_code,

ma json_encode non funziona bene con la stringa iso-8859-1, non appena c'è un carattere speciale, la stringa viene ritagliata.

consiglierei di usare var_export:

md5(var_export($array, true))

non così lento come serializzare, non così disturbato come json_encode


Non così veloce, l'opzione migliore è usare md4, anche var_export è lento
user956584

0

Attualmente la risposta più votata md5(serialize($array));non funziona bene con gli oggetti.

Considera il codice:

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

Anche se gli array sono diversi (contengono oggetti diversi), hanno lo stesso hash quando vengono utilizzati md5(serialize($array));. Quindi il tuo hash è inutile!

Per evitare questo problema, è possibile sostituire gli oggetti con il risultato di spl_object_hash()prima della serializzazione. Dovresti anche farlo in modo ricorsivo se il tuo array ha più livelli.

Il codice seguente ordina anche gli array in base alle chiavi, come suggerito da 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;
}

Ora puoi usare md5(serialize(replaceObjectsWithHashes($array))).

(Nota che l'array in PHP è di tipo valore. Quindi la replaceObjectsWithHashesfunzione NON modifica l'array originale.)


0

Non ho visto la soluzione così facilmente sopra, quindi ho voluto contribuire con una risposta più semplice. Per me, stavo ottenendo la stessa chiave finché non ho usato ksort (key sort):

Ordinato prima con Ksort, poi eseguito sha1 su json_encode:

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

esempio:

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

Output di array e hash modificati:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.