PHP cara terbaik untuk array multi-dimensi MD5?

120

Apa cara terbaik untuk menghasilkan MD5 (atau hash lainnya) dari array multi-dimensi?

Saya dapat dengan mudah menulis sebuah loop yang akan melintasi setiap tingkat array, menggabungkan setiap nilai menjadi string, dan hanya melakukan MD5 pada string.

Namun, ini tampaknya tidak praktis dan saya bertanya-tanya apakah ada fungsi funky yang akan mengambil array multi-dimensi, dan meng-hashnya.

Peter John
sumber

Jawaban:

261

(Fungsi dapat menyalin-n-tempel di bagian bawah)

Seperti disebutkan sebelumnya, berikut ini akan berhasil.

md5(serialize($array));

Namun, perlu dicatat bahwa (ironisnya) melakukan json_encode terasa lebih cepat:

md5(json_encode($array));

Faktanya, peningkatan kecepatan dua kali lipat di sini karena (1) json_encode sendiri bekerja lebih cepat daripada serialisasi, dan (2) json_encode menghasilkan string yang lebih kecil dan oleh karena itu lebih sedikit untuk ditangani md5.

Sunting: Berikut adalah bukti untuk mendukung klaim ini:

<?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 secara konsisten lebih dari 250% (2,5x) lebih cepat (seringkali lebih dari 300%) - ini bukanlah perbedaan yang sepele. Anda dapat melihat hasil pengujian dengan skrip langsung ini di sini:

Sekarang, satu hal yang perlu diperhatikan adalah array (1,2,3) akan menghasilkan MD5 yang berbeda sebagai array (3,2,1). Jika ini BUKAN yang Anda inginkan. Coba kode berikut:

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

Sunting: Ada beberapa pertanyaan, apakah membalikkan pesanan akan menghasilkan hasil yang sama. Jadi, saya telah melakukannya ( dengan benar ) di sini:

Seperti yang Anda lihat, hasilnya persis sama. Inilah tes ( dikoreksi ) yang awalnya dibuat oleh seseorang yang terkait dengan Drupal :

Dan untuk ukuran yang baik, berikut adalah fungsi / metode yang dapat Anda salin dan tempel (diuji di 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
sumber
47
LOL! Betulkah? Saya memilih untuk optimasi "lebih"? Pada kenyataannya, serialisasi PHP jauh lebih lambat. Saya akan memperbarui jawaban saya dengan bukti ...
Nathan JB
19
Apa yang telah dilakukan Nathan di sini sangat berharga bahkan jika seseorang tidak dapat melihat nilainya. Ini mungkin merupakan pengoptimalan yang berharga dalam beberapa situasi di luar konteks kita. Pengoptimalan mikro adalah keputusan yang buruk di beberapa tetapi tidak semua situasi
SeanDowney
13
Saya bukan orang yang mendukung pengoptimalan mikro demi itu, tetapi di mana ada peningkatan kinerja yang didokumentasikan tanpa kerja ekstra, lalu mengapa tidak menggunakannya.
bumperbox
2
Sebenarnya, sepertinya itu tergantung pada seberapa dalam lariknya. Saya kebetulan membutuhkan sesuatu yang perlu dijalankan secepat mungkin dan sementara POC Anda menunjukkan bahwa json_encode () ~ 300% lebih cepat, ketika saya mengubah variabel $ array dalam kode Anda ke use-case saya, itu kembali 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)(perhitungan present jelas salah). Array saya memiliki kedalaman hingga 2 level, jadi ingatlah bahwa (seperti biasa) jarak tempuh Anda mungkin berbeda.
samitny
3
Oke, saya tidak mengerti mengapa jawaban Nathan bukanlah jawaban teratas. Serius, gunakan serialisasi dan ganggu pengguna Anda dengan situs yang sangat lambat. Epic +1 @ NathanJ.Brauer!
ReSpawN
168
md5(serialize($array));
Brock Batsell
sumber
13
jika karena alasan tertentu Anda ingin mencocokkan hash (sidik jari), Anda mungkin ingin mempertimbangkan untuk menyortir larik "sort" atau "ksort", selain itu menerapkan semacam scrubbing / pembersihan mungkin juga diperlukan
farinspace
9
Serialize sangaaaat jauh lebih lambat dari json_encode dari jawaban kedua. Apakah server Anda senang dan gunakan json_encode! :)
s3m3n
3
Sepertinya Anda perlu melakukan benchmark pada array Anda sendiri untuk mengetahui apakah Anda harus menggunakan json_encode atau serialize. Bergantung pada lariknya, itu berbeda.
Ligemer
Saya yakin itu salah jalan, silakan cek penjelasan saya di bawah.
TermiT
1
@joelpittet - Tidak. Kedua contoh di link drupal tersebut memiliki bug. Lihat komentar di jawaban saya di bawah ini. ;) Misalnya dl.dropboxusercontent.com/u/4115701/Screenshots/…
Nathan JB
26

Saya bergabung dengan pesta yang sangat ramai dengan menjawab, tetapi ada pertimbangan penting bahwa tidak ada jawaban yang masih ada yang alamat. Nilai json_encode()dan serialize()keduanya bergantung pada urutan elemen dalam larik!

Berikut adalah hasil dari tidak mengurutkan dan mengurutkan array, pada dua array dengan nilai yang identik tetapi ditambahkan dalam urutan yang berbeda (kode di bagian bawah posting) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Oleh karena itu, dua metode yang saya rekomendasikan untuk melakukan hash sebuah array adalah:

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

Pilihan json_encode()atau serialize()harus ditentukan dengan menguji jenis data yang Anda gunakan . Dengan pengujian saya sendiri pada data tekstual dan numerik murni, jika kode tidak menjalankan loop ketat ribuan kali maka perbedaannya bahkan tidak layak untuk dijadikan tolok ukur. Saya pribadi menggunakan json_encode()untuk jenis data itu.

Berikut adalah kode yang digunakan untuk menghasilkan tes pengurutan di atas:

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

Implementasi deep_ksort () saya yang cepat, cocok dengan kasus ini, tetapi periksa sebelum digunakan pada proyek Anda sendiri:

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

Jawabannya sangat tergantung pada tipe data nilai array. Untuk string besar, gunakan:

md5(serialize($array));

Untuk string pendek dan bilangan bulat, gunakan:

md5(json_encode($array));

4 fungsi PHP bawaan dapat mengubah array menjadi string: serialize () , json_encode () , var_export () , print_r () .

Perhatikan: fungsi json_encode () melambat saat memproses array asosiatif dengan string sebagai nilai. Dalam hal ini pertimbangkan untuk menggunakan fungsi serialize () .

Hasil tes untuk array multi-dimensi dengan md5-hashes (32 char) pada kunci dan nilai:

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%

Hasil tes untuk larik multi-dimensi numerik:

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%

Sumber pengujian array asosiatif . Sumber pengujian larik numerik .

Alexander Yancharuk
sumber
Bisakah Anda menjelaskan apa itu string besar dan pendek ?
AL
1
@AL string pendek - string yang berisi kurang dari 25-30 karakter. string besar - semuanya berisi lebih dari 25-30 karakter.
Alexander Yancharuk
7

Selain dari jawaban Brock yang sangat baik (+1), pustaka hashing yang layak memungkinkan Anda memperbarui hash secara bertahap, jadi Anda harus dapat memperbarui setiap string secara berurutan, alih-alih harus membangun satu string raksasa.

Lihat: hash_update

Chris Jester-Young
sumber
perlu dicatat, bahwa metode ini tidak efisien jika Anda memperbarui dengan fragmen kecil; itu bagus untuk potongan besar file besar.
wrygiel
@wrygiel Itu tidak benar. Untuk MD5, kompresi selalu dilakukan dalam blok 64-byte (tidak peduli berapa ukuran "potongan besar" Anda), dan, jika Anda belum mengisi satu blok, tidak ada pemrosesan yang terjadi sampai blok tersebut terisi. (Saat Anda menyelesaikan hash, blok terakhir diisi hingga satu blok penuh, sebagai bagian dari pemrosesan akhir.) Untuk latar belakang selengkapnya, baca konstruksi Merkle-Damgard (yang semuanya didasarkan pada MD5, SHA-1, dan SHA-2 ).
Chris Jester-Young
Kamu benar. Saya benar-benar disesatkan oleh komentar di beberapa situs lain.
wrygiel
@wrygiel Itulah mengapa melakukan riset sendiri saat mengikuti ide yang "ditemukan di Internet" adalah hal yang bermanfaat. ;-) Dengan demikian, komentar terakhir itu mudah untuk saya tulis, karena saya sebenarnya menerapkan MD5 dari awal beberapa tahun yang lalu (untuk melatih keterampilan pemrograman Skema saya), jadi saya tahu cara kerjanya dengan sangat baik.
Chris Jester-Young
Inilah yang saya inginkan. Memindahkan dan menyalin data dalam jumlah besar ke dalam memori terkadang tidak dapat diterima. Jadi seperti jawaban lain menggunakan serialize () adalah ide yang sangat buruk dalam hal kinerja. Tetapi API ini masih hilang jika saya hanya ingin melakukan hash bagian dari String dari offset tertentu.
Jianwu Chen
4
md5(serialize($array));

Akan berfungsi, tetapi hash akan berubah tergantung pada urutan array (itu mungkin tidak masalah).

Max Wheeler
sumber
3

Perhatikan bahwa serializedan json_encodebertindak secara berbeda ketika datang ke array numerik di mana kuncinya tidak dimulai dari 0, atau array asosiatif. json_encodeakan menyimpan array seperti itu Object, jadi json_decodemengembalikan sebuah Object, di mana unserializeakan mengembalikan array dengan kunci yang sama persis.

Willem-Jan
sumber
3

Saya pikir ini bisa menjadi tip yang bagus:

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

Catatan penting tentang serialize()

Saya tidak menyarankan untuk menggunakannya sebagai bagian dari fungsi hashing karena dapat mengembalikan hasil yang berbeda untuk contoh berikut. Lihat contoh di bawah ini:

Contoh sederhana:

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

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

Menghasilkan

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

Tetapi kode berikut:

<?php

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

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

Keluaran:

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

Jadi, alih-alih objek kedua php cukup buat tautan "r: 2;" ke contoh pertama. Ini pasti cara yang baik dan benar untuk membuat serial data, tetapi itu dapat menyebabkan masalah dengan fungsi hashing Anda.

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

ada beberapa jawaban yang mengatakan untuk menggunakan json_code,

tetapi json_encode tidak berfungsi dengan baik dengan string iso-8859-1, segera setelah ada karakter khusus, string tersebut dipotong.

saya akan menyarankan untuk menggunakan var_export:

md5(var_export($array, true))

tidak selambat serialize, tidak disadap seperti json_encode

Bruno
sumber
Tidak terlalu cepat, opsi terbaik adalah menggunakan md4, var_export juga lambat
pengguna956584
0

Saat ini, jawaban yang paling banyak dipilih md5(serialize($array));tidak berfungsi dengan baik dengan objek.

Pertimbangkan kode:

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

Meskipun array berbeda (berisi objek yang berbeda), mereka memiliki hash yang sama saat digunakan md5(serialize($array));. Jadi hash Anda tidak berguna!

Untuk menghindari masalah tersebut, Anda dapat mengganti objek dengan hasil spl_object_hash()sebelum serialisasi. Anda juga harus melakukannya secara rekursif jika array Anda memiliki beberapa level.

Kode di bawah ini juga mengurutkan array berdasarkan kunci, seperti yang disarankan oleh 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;
}

Sekarang Anda bisa menggunakan md5(serialize(replaceObjectsWithHashes($array))).

(Perhatikan bahwa array dalam PHP adalah tipe nilai. Jadi replaceObjectsWithHashesfungsi JANGAN mengubah array asli.)

Damian Polac
sumber
0

Saya tidak melihat solusi dengan mudah di atas, jadi saya ingin memberikan jawaban yang lebih sederhana. Bagi saya, saya mendapatkan kunci yang sama sampai saya menggunakan ksort (semacam kunci):

Diurutkan terlebih dahulu dengan Ksort, lalu lakukan sha1 di json_encode:

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

contoh:

$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 dari array dan hash yang diubah:

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