PHP çok boyutlu MD5 dizisinin en iyi yolu?


120

Çok boyutlu bir dizinin MD5'ini (veya başka bir karmasını) oluşturmanın en iyi yolu nedir?

Dizinin her seviyesinde dolaşan, her değeri bir dizeye birleştiren ve MD5'i dizede gerçekleştiren bir döngü kolayca yazabilirim.

Ancak, bu en iyi ihtimalle külfetli görünüyor ve çok boyutlu bir diziyi alıp hash haline getirecek acayip bir işlev olup olmadığını merak ettim.

Yanıtlar:


260

(Altta kopyala ve yapıştırılabilir işlev)

Daha önce de belirtildiği gibi, aşağıdakiler çalışacaktır.

md5(serialize($array));

Bununla birlikte, (ironik bir şekilde) json_encode'un belirgin şekilde daha hızlı performans gösterdiğini belirtmek gerekir :

md5(json_encode($array));

Aslında, hız artışı burada iki katlıdır, çünkü (1) json_encode tek başına serileştirmeden daha hızlı performans gösterir ve (2) json_encode daha küçük bir dizi üretir ve bu nedenle md5'in işlemesi için daha azdır.

Düzenleme: İşte bu iddiayı destekleyen kanıtlar:

<?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 sürekli olarak% 250'nin (2,5 kat) üzerinde daha hızlıdır (genellikle% 300'ün üzerinde) - bu önemsiz bir fark değildir. Bu canlı komut dosyasıyla testin sonuçlarını burada görebilirsiniz:

Şimdi, dikkat edilmesi gereken bir şey, array (1,2,3) 'ün array (3,2,1) olarak farklı bir MD5 üreteceğidir. Eğer istediğin bu DEĞİL ise. Aşağıdaki kodu deneyin:

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

Düzenleme: Siparişi tersine çevirmenin aynı sonuçları verip vermeyeceği konusunda bazı sorular var. Yani, bunu ( doğru ) burada yaptım :

Gördüğünüz gibi sonuçlar tamamen aynı. İşte orijinal olarak Drupal ile ilgili biri tarafından oluşturulan ( düzeltilmiş ) test :

İyi bir önlem için, kopyalayıp yapıştırabileceğiniz bir işlev / yöntem aşağıda verilmiştir (5.3.3-1ubuntu9.5'te test edilmiştir):

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! Gerçekten mi? "Fazla" optimizasyonu için oy verdim mi? Gerçekte, PHP'nin serileştirilmesi önemli ölçüde daha yavaştır. Cevabımı kanıtlarla güncelleyeceğim ...
Nathan JB

19
Nathan'ın burada yaptıkları, değeri görülemese bile değerlidir. Bağlamımızın dışında kalan bazı durumlarda değerli bir optimizasyon olabilir. Mikro optimizasyon her durumda olmasa da bazı durumlarda kötü bir karardır
SeanDowney 23

13
Bunun uğruna mikro optimizasyon konusunda biri değilim, ancak fazladan çalışma olmadan belgelenmiş bir performans artışı varsa, o zaman neden kullanmayalım?
bumperbox

2
Aslında dizinin ne kadar derin olduğuna bağlı gibi görünüyor. Olabildiğince hızlı çalışması gereken bir şeye ihtiyacım var ve POC'niz json_encode () 'un ~% 300 daha hızlı olduğunu gösterirken, kodunuzdaki $ dizi değişkenini kullanım 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)durumuma değiştirdiğimde geri döndü (precent hesaplama açıkça yanlıştır). Dizim 2 seviyeye kadar derin, bu yüzden (her zamanki gibi) milinizin değişebileceğini unutmayın.
samitny

3
Tamam, Nathan'ın cevabının neden en iyi cevap olmadığını anlamıyorum. Cidden, serileştirmeyi kullanın ve çok yavaş bir siteyle kullanıcılarınızı rahatsız edin. Destansı +1 @ NathanJ.Brauer!
ReSpawN

168
md5(serialize($array));

13
Eğer herhangi bir nedenle hash'i (parmak izi) eşleştirmek istiyorsanız, "sort" veya "ksort" dizisini sıralamayı düşünebilirsiniz, ek olarak bir çeşit ovma / temizleme de gerekli olabilir
farinspace

9
Serialize, soooooooo, ikinci yanıttaki json_encode'dan çok daha yavaş. Sunucunuzu bir zevk haline getirin ve json_encode kullanın! :)
s3m3n

3
Görünüşe göre json_encode veya serialize kullanıp kullanmamanız gerektiğini anlamak için kendi dizinizi kıyaslamanız gerekiyor. Diziye bağlı olarak farklılık gösterir.
Ligemer

Bunun yanlış bir yol olduğuna inanıyorum, lütfen aşağıdaki açıklamamı kontrol edin.
TermiT

1
@joelpittet - Hayır. Bu drupal bağlantısındaki her iki örnekte de hatalar var. Aşağıdaki cevabımdaki yorumlara bakın. ;) Ör. Dl.dropboxusercontent.com/u/4115701/Screenshots/…
Nathan JB

26

Cevap vererek çok kalabalık bir partiye katılıyorum, ancak mevcut cevapların hiçbirinin hitap etmediği önemli bir düşünce var. Değeri json_encode()ve serialize()her ikisi de dizideki öğelerin sırasına bağlıdır!

Dizileri aynı değerlere sahip ancak farklı bir sırayla eklenen iki dizide sıralamanın ve sıralamanın sonuçları aşağıda verilmiştir (yazının altındaki kod) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Bu nedenle, bir diziye hashing uygulamak için önereceğim iki yöntem şöyle olacaktır:

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

Seçimi json_encode()veya serialize()gerektiği verilerin türüne yapılan testler sonucu tespit Eğer kullandığınız . Tamamen metinsel ve sayısal veriler üzerinde kendi testlerime göre, eğer kod binlerce kez sıkı bir döngüde çalışmıyorsa, o zaman aradaki fark kıyaslamaya bile değmez. Ben şahsen json_encode()bu tür veriler için kullanıyorum.

Yukarıdaki sıralama testini oluşturmak için kullanılan kod:

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

Benim hızlı deep_ksort () uygulamam bu duruma uyuyor, ancak kendi projelerinizde kullanmadan önce kontrol edin:

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

Cevap, büyük ölçüde dizi değerlerinin veri türlerine bağlıdır. Büyük dizeler için şunu kullanın:

md5(serialize($array));

Kısa dizeler ve tamsayılar için şunu kullanın:

md5(json_encode($array));

4 yerleşik PHP işlevi diziyi dizeye dönüştürebilir: serialize () , json_encode () , var_export () , print_r () .

Dikkat: json_encode () işlevi, değer olarak dizelerle ilişkili dizileri işlerken yavaşlar. Bu durumda serialize () işlevini kullanmayı düşünün .

Anahtarlarda ve değerlerde md5 karma (32 karakter) içeren çok boyutlu dizi için test sonuçları:

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%

Sayısal çok boyutlu dizi için test sonucu:

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%

İlişkisel dizi test kaynağı . Sayısal dizi testi kaynağı .


Lütfen büyük ve kısa dizelerin ne olduğunu açıklar mısınız?
AL

1
@AL kısa dizeler - 25-30'dan az karakter içeren dizeler. büyük dizeler - tümü 25-30'dan fazla karakter içerir.
Alexander Yancharuk

7

Brock'un mükemmel cevabının (+1) yanı sıra, herhangi bir düzgün hashing kitaplığı, hash'i aşamalı olarak güncellemenize izin verir, böylece her dizeyi sırayla güncellemeniz gerekir, bunun yerine bir dev dizi oluşturmak zorunda kalabilirsiniz.

Görmek: hash_update


küçük parçalarla güncelleme yapıyorsanız, bu yöntemin verimsiz olduğunu belirtmek gerekir; yine de büyük dosyaların büyük parçaları için iyidir.
wrygiel

@wrygiel Bu doğru değil. MD5 için, sıkıştırma her zaman 64 baytlık bloklar halinde yapılır ("büyük parçalarınızın" boyutu ne olursa olsun) ve henüz bir bloğu doldurmadıysanız, blok doldurulana kadar işlem gerçekleşmez. ( Karmayı sonlandırdığınızda , son işlemin bir parçası olarak son blok tam bloğa kadar doldurulur.) Daha fazla arka plan için Merkle-Damgard yapısını okuyun (MD5, SHA-1 ve SHA-2'nin tümü temel alınmıştır) ).
Chris Jester-Young

Haklısın. Başka bir siteye yapılan yorumla tamamen yanıltıldım.
wrygiel

@wrygiel Bu nedenle, "İnternette bulunan" bir fikri takip ederken kendi araştırmanızı yapmanız gerekir. ;-) Bunu söylerken, bu son yorumu yazmak benim için kolaydı, çünkü aslında MD5'i birkaç yıl önce sıfırdan uyguladım (Şema programlama becerilerimi uygulamak için), bu yüzden işleyişini çok iyi biliyorum.
Chris Jester-Young

Bu tam olarak istediğim şey. Bellekte büyük veri kamyonu taşımak ve kopyalamak bazen kabul edilemez. Yani serialize () kullanan diğer cevaplar gibi performans açısından çok kötü bir fikirdir. Ancak String'in yalnızca bir kısmını belirli bir ofsetten hash etmek istiyorsam bu API hala eksik.
Jianwu Chen

4
md5(serialize($array));

Çalışacak, ancak karma dizinin sırasına bağlı olarak değişecektir (bu önemli olmayabilir).


3

Unutmayın serializeve json_encodeanahtarların 0'da başlamadığı sayısal diziler veya ilişkilendirilebilir diziler söz konusu olduğunda farklı davranın. json_encodebu tür dizileri bir olarak depolar Object, bu nedenle json_decodebir Object, unserializetam olarak aynı anahtarlara sahip bir dizi döndürür.


3

Bunun iyi bir ipucu olabileceğini düşünüyorum:

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

Hakkında önemli not serialize()

Aşağıdaki örnekler için farklı sonuçlar döndürebileceğinden, hashing işlevinin bir parçası olarak kullanmanızı önermiyorum. Aşağıdaki örneği kontrol edin:

Basit örnek:

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

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

üretir

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

Ancak aşağıdaki kod:

<?php

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

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

Çıktı:

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

Yani ikinci nesne php yerine sadece "r: 2;" bağlantısını oluşturun. ilk etapta. Verileri serileştirmenin kesinlikle iyi ve doğru bir yolu, ancak hashing işlevinizle ilgili sorunlara yol açabilir.


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

json_code kullanmayı söyleyen birkaç cevap var,

ancak json_encode, iso-8859-1 dizesi ile düzgün çalışmaz, özel bir karakter olduğu anda dizge kırpılır.

var_export kullanmanızı tavsiye ederim:

md5(var_export($array, true))

serileştirme kadar yavaş değil, json_encode kadar hatalı değil


Çok hızlı değil, en iyi seçenek md4 kullanmaktır, var_export da yavaştır
user956584

0

Şu anda en çok oy alan cevap md5(serialize($array));nesnelerde pek iyi çalışmıyor.

Kodu düşünün:

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

Diziler farklı olsalar da (farklı nesneler içeriyorlar), kullanırken aynı hash'e sahipler md5(serialize($array));. Yani esrarın işe yaramaz!

Bu sorunu önlemek için, nesneleri spl_object_hash()serileştirmeden önce sonucuyla değiştirebilirsiniz . Dizinizin birden çok düzeyi varsa, bunu yinelemeli olarak da yapmalısınız.

Aşağıdaki kod, dotancohen'in önerdiği gibi dizileri de anahtarlara göre sıralar.

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

Şimdi kullanabilirsiniz md5(serialize(replaceObjectsWithHashes($array))).

(PHP'deki dizinin değer türü olduğunu unutmayın. Bu nedenle replaceObjectsWithHashesişlev orijinal diziyi DEĞİŞTİRMEYİN.)


0

Yukarıdaki çözümü o kadar kolay görmedim, bu yüzden daha basit bir cevaba katkıda bulunmak istedim. Benim için, ksort (anahtar sıralama) kullanana kadar aynı anahtarı alıyordum:

Önce Ksort ile sıralandı, sonra bir json_encode üzerinde sha1 yapıldı:

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

misal:

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

Değiştirilen dizilerin ve karmaların çıktısı:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.