Najlepszym sposobem PHP na wielowymiarową tablicę MD5?


120

Jaki jest najlepszy sposób na wygenerowanie MD5 (lub dowolnego innego skrótu) wielowymiarowej tablicy?

Mógłbym łatwo napisać pętlę, która przeszłaby przez każdy poziom tablicy, łącząc każdą wartość w ciąg i po prostu wykonując MD5 na ciągu.

Wydaje się to jednak w najlepszym razie uciążliwe i zastanawiałem się, czy istnieje funky funkcja, która pobierałaby wielowymiarową tablicę i haszowała ją.

Odpowiedzi:


260

(Funkcja kopiowania i wklejania na dole)

Jak wspomniano wcześniej, poniższe będą działać.

md5(serialize($array));

Warto jednak zauważyć, że (jak na ironię) json_encode działa zauważalnie szybciej:

md5(json_encode($array));

W rzeczywistości wzrost szybkości jest tutaj dwukrotny, ponieważ (1) sam json_encode działa szybciej niż serialize, a (2) json_encode generuje mniejszy ciąg, a zatem mniejszy dla md5.

Edycja: Oto dowody na poparcie tego twierdzenia:

<?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 jest konsekwentnie ponad 250% (2,5x) szybsze (często ponad 300%) - to nie jest trywialna różnica. Możesz zobaczyć wyniki testu z tym skryptem na żywo tutaj:

Jedną rzeczą wartą uwagi jest to, że array (1,2,3) wyprodukuje inne MD5 niż array (3,2,1). Jeśli to NIE jest to, czego chcesz. Wypróbuj następujący kod:

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

Edycja: pojawiło się pytanie, czy odwrócenie kolejności dałoby takie same wyniki. Więc zrobiłem to ( poprawnie ) tutaj:

Jak widać, wyniki są dokładnie takie same. Oto ( poprawiony ) test pierwotnie stworzony przez kogoś związanego z Drupalem :

I na dokładkę, oto funkcja / metoda, którą możesz skopiować i wkleić (przetestowana w 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! Naprawdę? Zostałem oddany głosowaniu za „ponad” optymalizacją? W rzeczywistości serializacja PHP jest znacznie wolniejsza. Uzupełnię swoją odpowiedź dowodami ...
Nathan JB,

19
To, co tu zrobił Nathan, jest cenne, nawet jeśli nie można tego dostrzec. Może to być cenna optymalizacja w niektórych sytuacjach, które są poza naszym kontekstem. Mikro optymalizacja to zła decyzja w niektórych, ale nie we wszystkich sytuacjach
SeanDowney,

13
Nie jestem zwolennikiem mikrooptymalizacji ze względu na to, ale tam, gdzie udokumentowany jest wzrost wydajności bez dodatkowej pracy, dlaczego nie skorzystać.
bumperbox

2
Właściwie wygląda na to, że zależy to od głębokości tablicy. Tak się składa, że ​​potrzebuję czegoś, co musi działać tak szybko, jak to możliwe i chociaż twój POC pokazuje, że json_encode () jest ~ 300% szybszy, kiedy zmieniłem zmienną tablicową $ w twoim kodzie na mój przypadek użycia, zwrócił 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)(obliczenie precent jest oczywiście błędny). Moja tablica ma do 2 poziomów głębokości, więc pamiętaj, że (jak zwykle) Twój przebieg może się różnić.
samitny

3
OK, nie rozumiem, dlaczego odpowiedź Nathana nie jest najlepszą odpowiedzią. Poważnie, używaj serializacji i drażnij swoich użytkowników ogromną powolną witryną. Epic +1 @ NathanJ.Brauer!
ReSpawN

168
md5(serialize($array));

13
jeśli z jakiegoś powodu chcesz dopasować hash (odcisk palca), możesz rozważyć sortowanie tablicy "sort" lub "ksort", dodatkowo zaimplementowanie jakiegoś rodzaju czyszczenia / czyszczenia może być również potrzebne
farinspace

9
Serialize jest baaaaardzo dużo wolniejszy niż json_encode z drugiej odpowiedzi. Spraw, aby Twój serwer był przyjemnością i używaj json_encode! :)
s3m3n

3
Wygląda na to, że musisz przetestować własną tablicę, aby dowiedzieć się, czy powinieneś użyć json_encode, czy serialize. W zależności od tablicy różni się.
Ligemer

Uważam, że to zły sposób, sprawdź moje wyjaśnienie poniżej.
TermiT

1
@joelpittet - Nie. Oba przykłady w linku drupal mają błędy. Zobacz komentarze w mojej odpowiedzi poniżej. ;) Np. Dl.dropboxusercontent.com/u/4115701/Screenshots/ ...
Nathan JB

26

Odpowiadam na bardzo zatłoczoną imprezę, ale należy wziąć pod uwagę, że żadna z istniejących odpowiedzi nie dotyczy. Wartość json_encode()i serialize()obie zależą od kolejności elementów w tablicy!

Oto wyniki braku sortowania i sortowania tablic na dwóch tablicach z identycznymi wartościami, ale dodanymi w innej kolejności (kod na dole postu) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Dlatego dwie metody, które poleciłbym do haszowania tablicy , to:

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

Wybór json_encode()lub serialize()należy określić, testując typ danych, których używasz . Na podstawie moich własnych testów na danych czysto tekstowych i liczbowych, jeśli kod nie wykonuje ciasnej pętli tysiące razy, różnica nie jest nawet warta porównania. Osobiście używam json_encode()do tego typu danych.

Oto kod użyty do wygenerowania powyższego testu sortowania:

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

Moja szybka implementacja deep_ksort () pasuje do tego przypadku, ale sprawdź to przed użyciem we własnych projektach:

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

Odpowiedź w dużym stopniu zależy od typów danych wartości tablicowych. Do dużych strun użyj:

md5(serialize($array));

Dla krótkich ciągów i liczb całkowitych użyj:

md5(json_encode($array));

4 wbudowane funkcje PHP mogą przekształcić tablicę w łańcuch znaków: serialize () , json_encode () , var_export () , print_r () .

Uwaga: funkcja json_encode () zwalnia podczas przetwarzania tablic asocjacyjnych z ciągami znaków jako wartościami. W takim przypadku rozważ użycie funkcji serialize () .

Wyniki testu dla wielowymiarowej tablicy z hashami md5 (32 znaki) w kluczach i wartościach:

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%

Wynik testu dla wielowymiarowej tablicy numerycznej:

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%

Źródło testu tablicy asocjacyjnej . Numeryczne źródło testu tablicy .


Czy możesz wyjaśnić, czym są długie i krótkie struny ?
AL

1
@AL short strings - łańcuchy zawierające mniej niż 25-30 znaków. duże łańcuchy - wszystkie zawierające więcej niż 25-30 znaków.
Alexander Yancharuk

7

Oprócz doskonałej odpowiedzi Brocka (+1), każda przyzwoita biblioteka haszująca umożliwia aktualizowanie skrótu w przyrostach, więc powinieneś być w stanie aktualizować każdy ciąg sekwencyjnie, zamiast tworzyć jeden gigantyczny ciąg.

Widzieć: hash_update


warto zauważyć, że ta metoda jest nieefektywna, jeśli aktualizujesz za pomocą małych fragmentów; jest jednak dobry dla dużych fragmentów dużych plików.
wrygiel

@wrygiel To nieprawda. W przypadku MD5 kompresja jest zawsze wykonywana w blokach 64-bajtowych (bez względu na rozmiar twoich „dużych porcji”), a jeśli jeszcze nie zapełniłeś bloku, żadne przetwarzanie nie nastąpi, dopóki blok nie zostanie zapełniony. (Po sfinalizowaniu skrótu ostatni blok jest uzupełniany do pełnego bloku, jako część końcowego przetwarzania). Aby uzyskać więcej informacji, przeczytaj konstrukcję Merkle-Damgard (na której bazują MD5, SHA-1 i SHA-2 ).
Chris Jester-Young,

Masz rację. Całkowicie zmylił mnie komentarz na innej stronie.
wrygiel

@wrygiel Dlatego opłaca się przeprowadzić własne badania, kierując się pomysłem „znalezionym w Internecie”. ;-) Mówiąc tak, ten ostatni komentarz był dla mnie łatwy do napisania, ponieważ faktycznie wdrożyłem MD5 od zera kilka lat temu (aby przećwiczyć moje umiejętności programowania Scheme), więc bardzo dobrze znam jego działanie.
Chris Jester-Young,

Właśnie tego chcę. Przenoszenie i kopiowanie dużych ciężarówek danych w pamięci jest czasami nie do przyjęcia. Tak więc, podobnie jak inne odpowiedzi, użycie serialize () jest bardzo złym pomysłem pod względem wydajności. Ale tego interfejsu API nadal brakuje, jeśli chcę haszować tylko część ciągu z określonego przesunięcia.
Jianwu Chen

4
md5(serialize($array));

Będzie działać, ale wartość skrótu będzie się zmieniać w zależności od kolejności tablicy (choć może to nie mieć znaczenia).


3

Zauważ to serializei json_encodezachowuj się inaczej, jeśli chodzi o tablice numeryczne, w których klucze nie zaczynają się od 0, lub tablice asocjacyjne. json_encodezapisze takie tablice jako an Object, więc json_decodezwraca an Object, gdzie unserializezwróci tablicę z dokładnie tymi samymi kluczami.


3

Myślę, że to może być dobra wskazówka:

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

Ważna uwaga dotycząca serialize()

Nie polecam używania go jako części funkcji haszującej, ponieważ może zwrócić różne wyniki dla poniższych przykładów. Sprawdź poniższy przykład:

Prosty przykład:

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

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

Produkuje

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

Ale następujący kod:

<?php

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

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

Wynik:

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

Więc zamiast drugiego obiektu php po prostu utwórz link "r: 2;" do pierwszej instancji. Jest to zdecydowanie dobry i poprawny sposób serializacji danych, ale może to prowadzić do problemów z funkcją haszowania.


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

jest kilka odpowiedzi mówiących o używaniu json_code,

ale json_encode nie działa dobrze z ciągiem iso-8859-1, gdy tylko pojawi się specjalny znak, ciąg jest przycinany.

radziłbym skorzystać z var_export:

md5(var_export($array, true))

nie tak powolny jak serializowanie, nie tak bugowany jak json_encode


Nie tak szybko, najlepszą opcją jest użycie md4, var_export jest również wolny
user956584

0

Obecnie najbardziej md5(serialize($array));pozytywna odpowiedź nie działa dobrze z obiektami.

Rozważ kod:

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

Mimo że tablice są różne (zawierają różne obiekty), mają ten sam skrót podczas używania md5(serialize($array));. Więc twój haszysz jest bezużyteczny!

Aby uniknąć tego problemu, możesz zastąpić obiekty wynikiem spl_object_hash()przed serializacją. Powinieneś to również zrobić rekurencyjnie, jeśli twoja tablica ma wiele poziomów.

Poniższy kod również sortuje tablice według kluczy, zgodnie z sugestią 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;
}

Teraz możesz użyć md5(serialize(replaceObjectsWithHashes($array))).

(Zauważ, że tablica w PHP jest typu wartości. Więc replaceObjectsWithHashesfunkcja NIE zmienia oryginalnej tablicy.)


0

Powyżej nie widziałem rozwiązania tak łatwo, więc chciałem podać prostszą odpowiedź. U mnie otrzymywałem ten sam klucz, dopóki nie użyłem ksort (sortowanie kluczy):

Najpierw posortowano za pomocą Ksort, a następnie wykonano sha1 na json_encode:

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

przykład:

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

Wyjście zmienionych tablic i skrótów:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.