PHP melhor maneira de matriz multidimensional MD5?

120

Qual é a melhor maneira de gerar um MD5 (ou qualquer outro hash) de uma matriz multidimensional?

Eu poderia facilmente escrever um loop que atravessaria cada nível do array, concatenando cada valor em uma string e simplesmente executando o MD5 na string.

No entanto, isso parece complicado na melhor das hipóteses e eu me perguntei se havia uma função funky que pegaria um array multidimensional e faria o hash.

Peter john
fonte

Respostas:

260

(Função de copiar e colar na parte inferior)

Conforme mencionado anteriormente, o seguinte funcionará.

md5(serialize($array));

No entanto, é importante notar que (ironicamente) json_encode tem um desempenho visivelmente mais rápido:

md5(json_encode($array));

Na verdade, o aumento de velocidade é duplo aqui, já que (1) json_encode sozinho executa mais rápido do que serializar e (2) json_encode produz uma string menor e, portanto, menos para o md5 manipular.

Edit: Aqui estão as evidências para apoiar esta afirmação:

<?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 é consistentemente mais de 250% (2,5x) mais rápido (frequentemente mais de 300%) - esta não é uma diferença trivial. Você pode ver os resultados do teste com este script ao vivo aqui:

Agora, uma coisa a se notar é que array (1,2,3) produzirá um MD5 diferente como array (3,2,1). Se NÃO é isso que você deseja. Experimente o seguinte 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));

Edit: Tem havido alguma dúvida sobre se inverter a ordem produziria os mesmos resultados. Então, eu fiz isso ( corretamente ) aqui:

Como você pode ver, os resultados são exatamente os mesmos. Aqui está o teste ( corrigido ) originalmente criado por alguém relacionado ao Drupal :

E para uma boa medida, aqui está uma função / método que você pode copiar e colar (testado em 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
fonte
47
RI MUITO! Realmente? Eu fui votado a favor da otimização "acima"? Na realidade, a serialização do PHP é significativamente mais lenta. Vou atualizar minha resposta com evidências ...
Nathan JB
19
O que Nathan fez aqui é valioso, mesmo que não se veja o valor disso. Pode ser uma otimização valiosa em algumas situações que estão fora do nosso contexto. A micro otimização é uma decisão ruim em algumas, mas não em todas as situações
SeanDowney
13
Não sou de micro-otimização só por fazer, mas onde há um aumento de desempenho documentado sem trabalho extra, por que não usá-lo.
bumperbox
2
Na verdade, parece que depende da profundidade do array. Acontece que preciso de algo que precisa ser executado o mais rápido possível e enquanto seu POC mostra que json_encode () é ~ 300% mais rápido, quando mudei a variável $ array em seu código para o meu caso de uso, ele retornou 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)(o cálculo anterior está obviamente incorreto). Meu array tem até 2 níveis de profundidade, então lembre-se de que (como sempre) sua milhagem pode variar.
samitny
3
Ok, não vejo por que a resposta de Nathan não é a resposta principal. Sério, use serializar e irritar seus usuários com um site imenso e lento. Epic +1 @ NathanJ.Brauer!
ReSpawN
168
md5(serialize($array));
Brock Batsell
fonte
13
se por algum motivo você deseja combinar o hash (impressão digital), você pode querer considerar a classificação do array "sort" ou "ksort", além disso, a implementação de algum tipo de depuração / limpeza também pode ser necessária
farinspace
9
Serializar é tãããããão mais lento do que json_encode na segunda resposta. Faça um prazer com o seu servidor e use json_encode! :)
s3m3n
3
Parece que você precisa avaliar seu próprio array para descobrir se deve usar json_encode ou serializar. Dependendo da matriz, é diferente.
Ligemer
acredito que seja um caminho errado, verifique minha explicação abaixo.
TermiT,
1
@joelpittet - Não. Ambos os exemplos nesse link drupal têm bugs. Veja os comentários na minha resposta abaixo. ;) Por exemplo, dl.dropboxusercontent.com/u/4115701/Screenshots/…
Nathan JB
26

Estou entrando em uma festa muito lotada respondendo, mas há uma consideração importante que nenhuma das respostas existentes aborda. O valor de json_encode()e serialize()ambos dependem da ordem dos elementos no array!

Aqui estão os resultados de não classificar e classificar os arrays, em dois arrays com valores idênticos, mas adicionados em uma ordem diferente (código na parte inferior da postagem) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Portanto, os dois métodos que eu recomendaria para fazer o hash de uma matriz seriam:

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

A escolha de json_encode()ouserialize() deve ser determinada testando o tipo de dados que você está usando . Pelos meus próprios testes em dados puramente textuais e numéricos, se o código não estiver executando um loop restrito milhares de vezes, a diferença nem vale a pena ser comparada. Eu pessoalmente usojson_encode() para esse tipo de dados.

Aqui está o código usado para gerar o teste de classificação acima:

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

Minha implementação rápida de deep_ksort () se encaixa neste caso, mas verifique antes de usar em seus próprios projetos:

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

A resposta depende muito dos tipos de dados dos valores da matriz. Para cordas grandes, use:

md5(serialize($array));

Para strings curtas e inteiros, use:

md5(json_encode($array));

4 funções embutidas do PHP podem transformar array em string: serialize () , json_encode () , var_export () , print_r () .

Aviso: a função json_encode () fica mais lenta enquanto processa arrays associativos com strings como valores. Neste caso, considere usar a função serialize () .

Resultados do teste para matriz multidimensional com hashes md5 (32 caracteres) em chaves e 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 do teste para matriz multidimensional numérica:

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%

Fonte de teste de matriz associativa . Fonte de teste de matriz numérica .

Alexander Yancharuk
fonte
Você pode explicar o que são cordas grandes e curtas ?
AL
1
@AL strings curtas - strings que contêm menos de 25-30 caracteres. strings grandes - todas contendo mais de 25-30 caracteres.
Alexander Yancharuk,
7

Além da excelente resposta de Brock (+1), qualquer biblioteca de hash decente permite que você atualize o hash em incrementos, então você deve ser capaz de atualizar cada string sequencialmente, ao invés de ter que construir uma string gigante.

Vejo: hash_update

Chris Jester-Young
fonte
é importante notar que esse método é ineficiente se você estiver atualizando com pequenos fragmentos; é bom para grandes pedaços de arquivos enormes.
wrygiel de
@wrygiel Isso não é verdade. Para MD5, a compactação é sempre feita em blocos de 64 bytes (não importa o tamanho de seus "grandes pedaços") e, se você ainda não preencheu um bloco, nenhum processamento acontece até que o bloco seja preenchido. (Quando você finaliza o hash, o último bloco é preenchido até um bloco completo, como parte do processamento final.) Para obter mais informações, leia a construção Merkle-Damgard (em que MD5, SHA-1 e SHA-2 são todos baseados )
Chris Jester-Young
Você está certo. Fui totalmente enganado por um comentário em algum outro site.
wrygiel de
@wrygiel É por isso que vale a pena fazer sua própria pesquisa ao seguir uma ideia "encontrada na Internet". ;-) Dito isso, esse último comentário foi fácil para mim escrever, porque eu realmente implementei o MD5 do zero alguns anos atrás (para praticar minhas habilidades de programação em Scheme), então eu conheço seu funcionamento muito bem.
Chris Jester-Young de
Isso é exatamente o que eu quero. Mover e copiar um grande caminhão de dados na memória às vezes não é aceitável. Então, como outras respostas, usar serialize () é uma ideia muito ruim em termos de desempenho. Mas essa API ainda está faltando se eu quiser apenas fazer o hash de parte da String de um determinado deslocamento.
Jianwu Chen
4
md5(serialize($array));

Funcionará, mas o hash mudará dependendo da ordem do array (isso pode não importar).

Max Wheeler
fonte
3

Observe isso serializee json_encodeaja de maneira diferente quando se trata de matrizes numéricas em que as chaves não começam em 0 ou de matrizes associativas. json_encodeirá armazenar tais arrays como um Object, então json_decoderetorna um Object, onde unserializeirá retornar um array com exatamente as mesmas chaves.

Willem-Jan
fonte
3

Acho que essa poderia ser uma boa dica:

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

Nota importante sobre serialize()

Eu não recomendo usá-lo como parte da função de hashing porque ele pode retornar resultados diferentes para os exemplos a seguir. Veja o exemplo abaixo:

Exemplo simples:

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

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

Produz

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

Mas o seguinte código:

<?php

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

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

Resultado:

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

Portanto, em vez do segundo objeto php, apenas crie o link "r: 2;" para a primeira instância. É definitivamente uma maneira boa e correta de serializar dados, mas pode levar a problemas com sua função de hashing.

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

existem várias respostas dizendo para usar json_code,

mas json_encode não funciona bem com a string iso-8859-1, assim que houver um caractere especial, a string é cortada.

eu aconselharia usar var_export:

md5(var_export($array, true))

não tão lento quanto serializar, não tão bugado quanto json_encode

Bruno
fonte
Não tão rápido, a melhor opção é usar md4, var_export também é lento
user956584
0

Atualmente, a resposta mais votada md5(serialize($array));não funciona bem com objetos.

Considere o código:

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

Mesmo que os arrays sejam diferentes (eles contêm objetos diferentes), eles têm o mesmo hash quando usados md5(serialize($array));. Então seu hash é inútil!

Para evitar esse problema, você pode substituir os objetos com o resultado de spl_object_hash()antes de serializar. Você também deve fazer isso recursivamente se sua matriz tiver vários níveis.

O código abaixo também classifica os arrays por chaves, como sugerido pelo 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;
}

Agora você pode usar md5(serialize(replaceObjectsWithHashes($array))) .

(Observe que a matriz em PHP é o tipo de valor. Portanto, a replaceObjectsWithHashesfunção NÃO altere a matriz original.)

Damian Polac
fonte
0

Não vi a solução acima tão facilmente, então queria contribuir com uma resposta mais simples. Para mim, eu estava recebendo a mesma chave até usar ksort (classificação de chave):

Ordenado primeiro com Ksort, em seguida, executado sha1 em um json_encode:

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

exemplo:

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

Saída de matrizes e hashes alterados:

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