¿PHP es la mejor forma de matriz multidimensional MD5?

120

¿Cuál es la mejor manera de generar un MD5 (o cualquier otro hash) de una matriz multidimensional?

Podría escribir fácilmente un bucle que atraviese cada nivel de la matriz, concatenando cada valor en una cadena y simplemente realizando el MD5 en la cadena.

Sin embargo, esto parece engorroso en el mejor de los casos y me preguntaba si habría una función funky que tomaría una matriz multidimensional y la modificaría.

Pedro Juan
fuente

Respuestas:

260

(Función copiar y pegar en la parte inferior)

Como se mencionó anteriormente, lo siguiente funcionará.

md5(serialize($array));

Sin embargo, vale la pena señalar que (irónicamente) json_encode funciona notablemente más rápido:

md5(json_encode($array));

De hecho, el aumento de velocidad es el doble aquí ya que (1) json_encode solo funciona más rápido que serializar, y (2) json_encode produce una cadena más pequeña y por lo tanto menos para que md5 la maneje.

Editar: Aquí hay evidencia para respaldar esta afirmación:

<?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 es consistentemente más de 250% (2.5x) más rápido (a menudo más de 300%); esta no es una diferencia trivial. Puede ver los resultados de la prueba con este script en vivo aquí:

Ahora, una cosa a tener en cuenta es que la matriz (1,2,3) producirá un MD5 diferente como matriz (3,2,1). Si esto NO es lo que quieres. Prueba el siguiente código:

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

Editar: Ha habido algunas dudas sobre si invertir el orden produciría los mismos resultados. Entonces, lo he hecho ( correctamente ) aquí:

Como puede ver, los resultados son exactamente los mismos. Aquí está la prueba ( corregida ) creada originalmente por alguien relacionado con Drupal :

Y en buena medida, aquí hay una función / método que puede copiar y pegar (probado en 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
fuente
47
Jajaja De Verdad? ¿Me votaron por "sobre" optimización? En realidad, la serialización de PHP es significativamente más lenta. Actualizaré mi respuesta con evidencia ...
Nathan JB
19
Lo que Nathan ha hecho aquí es valioso incluso si uno no puede ver el valor de ello. Puede ser una optimización valiosa en algunas situaciones que están fuera de nuestro contexto. La microoptimización es una mala decisión en algunas situaciones, pero no en todas
SeanDowney
13
No soy de los que están a favor de la microoptimización por el simple hecho de hacerlo, pero cuando hay un aumento de rendimiento documentado sin trabajo adicional, ¿por qué no usarlo?
parachoques
2
En realidad, parece que depende de la profundidad de la matriz. Sucede que necesito algo que deba ejecutarse lo más rápido posible y, aunque su POC muestra que json_encode () es ~ 300% más rápido, cuando cambié la variable $ array en su código a mi caso de uso, devolvió 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)(el cálculo anterior es obviamente incorrecto). Mi matriz tiene hasta 2 niveles de profundidad, así que tenga en cuenta que (como de costumbre) su kilometraje puede variar.
Samitny
3
De acuerdo, no veo por qué la respuesta de Nathan no es la respuesta principal. En serio, usa serializar y molesta a tus usuarios con un sitio inmensamente lento. Epic +1 @ NathanJ.Brauer!
ReSpawN
168
md5(serialize($array));
Brock Batsell
fuente
13
si por alguna razón desea hacer coincidir el hash (huella digital), puede considerar ordenar la matriz "sort" o "ksort", además, podría ser necesario implementar algún tipo de limpieza / limpieza
farinspace
9
Serialize es muuuucho más lento que json_encode de la segunda respuesta. ¡Haga que su servidor sea un placer y use json_encode! :)
s3m3n
3
Parece que necesita comparar su propia matriz para averiguar si debe usar json_encode o serializar. Dependiendo de la matriz, difiere.
Ligemer
Creo que es una forma incorrecta, consulte mi explicación a continuación.
Termino
1
@joelpittet - No. Ambos ejemplos en ese enlace de drupal tienen errores. Vea los comentarios en mi respuesta a continuación. ;) Por ejemplo, dl.dropboxusercontent.com/u/4115701/Screenshots/…
Nathan JB
26

Me uniré a una fiesta muy concurrida al responder, pero hay una consideración importante que ninguna de las respuestas existentes aborda. ¡El valor de json_encode()y serialize()ambos dependen del orden de los elementos en la matriz!

Estos son los resultados de no ordenar y ordenar las matrices, en dos matrices con valores idénticos pero agregadas en un orden diferente (código en la parte inferior de la publicación) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Por lo tanto, los dos métodos que recomendaría para hacer hash en una matriz serían:

// 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 elección del json_encode()o serialize()debe ser determinado por las pruebas del tipo de datos que se está utilizando . Según mis propias pruebas con datos puramente textuales y numéricos, si el código no se ejecuta en un bucle cerrado miles de veces, la diferencia ni siquiera vale la pena compararla. Yo personalmente usojson_encode() para ese tipo de datos.

Aquí está el código utilizado para generar la prueba de clasificación anterior:

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

Mi implementación rápida de deep_ksort (), se ajusta a este caso, pero compruébalo antes de usarlo en tus propios proyectos:

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

La respuesta depende en gran medida de los tipos de datos de los valores de matriz. Para cuerdas grandes use:

md5(serialize($array));

Para cadenas cortas y enteros use:

md5(json_encode($array));

4 funciones PHP integradas pueden transformar una matriz en una cadena: serialize () , json_encode () , var_export () , print_r () .

Aviso: la función json_encode () se ralentiza mientras procesa matrices asociativas con cadenas como valores. En este caso, considere utilizar la función serialize () .

Resultados de la prueba para una matriz multidimensional con md5-hashes (32 caracteres) en claves y valores:

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%

Resultado de la prueba para una matriz numérica multidimensional:

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%

Fuente de prueba de matriz asociativa . Fuente de prueba de matriz numérica .

Alexander Yancharuk
fuente
¿Puede explicar qué son cadenas grandes y cortas ?
AL
1
@AL cadenas cortas : cadenas que contienen menos de 25-30 caracteres. cadenas grandes : todas contienen más de 25-30 caracteres.
Alexander Yancharuk
7

Aparte de la excelente respuesta de Brock (+1), cualquier biblioteca de hash decente le permite actualizar el hash en incrementos, por lo que debería poder actualizar con cada cadena secuencialmente, en lugar de tener que construir una cadena gigante.

Ver: hash_update

Chris Jester-Young
fuente
Vale la pena señalar que este método es ineficaz si está actualizando con pequeños fragmentos; aunque es bueno para grandes trozos de archivos enormes.
wrygiel
@wrygiel Eso no es cierto. Para MD5, la compresión siempre se realiza en bloques de 64 bytes (sin importar el tamaño de sus "trozos grandes") y, si aún no ha llenado un bloque, no se procesa hasta que se llena el bloque. (Cuando finaliza el hash, el último bloque se rellena hasta un bloque completo, como parte del procesamiento final). Para obtener más antecedentes, lea la construcción Merkle-Damgard (en la que se basan MD5, SHA-1 y SHA-2 ).
Chris Jester-Young
Tienes razón. Me engañó totalmente un comentario en otro sitio.
wrygiel
@wrygiel Es por eso que vale la pena hacer tu propia investigación cuando sigues una idea "encontrada en Internet". ;-) Al decir esto, ese último comentario fue fácil de escribir para mí, porque de hecho implementé MD5 desde cero hace unos años (para practicar mis habilidades de programación de Scheme), así que conozco muy bien su funcionamiento.
Chris Jester-Young
Eso es exactamente lo que quiero. A veces no es aceptable mover y copiar grandes cantidades de datos en la memoria. Entonces, al igual que otras respuestas, usar serialize () es una muy mala idea en términos de rendimiento. Pero esta API aún falta si solo quiero hash parte de la Cadena de un cierto desplazamiento.
Jianwu Chen
4
md5(serialize($array));

Funcionará, pero el hash cambiará según el orden de la matriz (aunque puede que no importe).

Max Wheeler
fuente
3

Tenga en cuenta eso serializey json_encodeactúe de manera diferente cuando se trata de matrices numéricas donde las claves no comienzan en 0, o matrices asociativas. json_encodealmacenará tales matrices como an Object, por lo que json_decodedevuelve an Object, donde unserializedevolverá una matriz con exactamente las mismas claves.

Willem-Jan
fuente
3

Creo que este podría ser un buen consejo:

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
fuente
2

Nota importante sobre serialize()

No recomiendo usarlo como parte de la función hash porque puede devolver un resultado diferente para los siguientes ejemplos. Mira el ejemplo a continuación:

Ejemplo simple:

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

Pero el siguiente código:

<?php

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

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

Salida:

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

Entonces, en lugar del segundo objeto php, simplemente cree el enlace "r: 2;" a la primera instancia. Definitivamente es una forma buena y correcta de serializar datos, pero puede provocar problemas con la función hash.

TermiT
fuente
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
fuente
1

hay varias respuestas que dicen usar json_code,

pero json_encode no funciona bien con la cadena iso-8859-1, tan pronto como hay un carácter especial, la cadena se recorta.

Aconsejaría usar var_export:

md5(var_export($array, true))

no tan lento como serializar, no tan bug como json_encode

Bruno
fuente
No tan rápido, la mejor opción es usar md4, var_export también es lento
user956584
0

Actualmente, la respuesta más votada md5(serialize($array));no funciona bien con objetos.

Considere el código:

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

Aunque las matrices son diferentes (contienen diferentes objetos), tienen el mismo hash cuando se usan md5(serialize($array));. ¡Entonces tu hachís es inútil!

Para evitar ese problema, puede reemplazar objetos con el resultado de spl_object_hash()antes de serializar. También debe hacerlo de forma recursiva si su matriz tiene varios niveles.

El código siguiente también ordena las matrices por claves, como sugirió 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;
}

Ahora puedes usar md5(serialize(replaceObjectsWithHashes($array))).

(Tenga en cuenta que la matriz en PHP es de tipo valor. Por lo tanto, la replaceObjectsWithHashesfunción NO cambia la matriz original).

Damian Polac
fuente
0

No vi la solución tan fácilmente arriba, así que quería contribuir con una respuesta más simple. Para mí, obtenía la misma clave hasta que usé ksort (clasificación de clave):

Ordenado primero con Ksort, luego realizado sha1 en un json_encode:

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

ejemplo:

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

Salida de matrices y hashes alterados:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
Mike Q
fuente