PHP cách tốt nhất để MD5 mảng đa chiều?


120

Cách tốt nhất để tạo MD5 (hoặc bất kỳ hàm băm nào khác) của một mảng nhiều chiều là gì?

Tôi có thể dễ dàng viết một vòng lặp đi qua từng cấp của mảng, nối từng giá trị thành một chuỗi và chỉ cần thực hiện MD5 trên chuỗi.

Tuy nhiên, điều này có vẻ rườm rà và tôi tự hỏi liệu có một hàm funky nào sẽ lấy một mảng đa chiều và băm nó.

Câu trả lời:


260

(Chức năng sao chép-n-dán-có thể ở dưới cùng)

Như đã đề cập trước đó, những điều sau đây sẽ hoạt động.

md5(serialize($array));

Tuy nhiên, cần lưu ý rằng (trớ trêu thay) json_encode hoạt động nhanh hơn đáng kể :

md5(json_encode($array));

Trên thực tế, tốc độ tăng gấp hai lần ở đây vì (1) json_encode một mình hoạt động nhanh hơn so với serialize và (2) json_encode tạo ra một chuỗi nhỏ hơn và do đó md5 xử lý ít hơn.

Chỉnh sửa: Đây là bằng chứng hỗ trợ tuyên bố này:

<?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 luôn nhanh hơn 250% (2,5 lần) (thường trên 300%) - đây không phải là một sự khác biệt nhỏ. Bạn có thể xem kết quả của bài kiểm tra với kịch bản trực tiếp này tại đây:

Bây giờ, một điều cần lưu ý là mảng (1,2,3) sẽ tạo ra một MD5 khác như mảng (3,2,1). Nếu đây KHÔNG phải là điều bạn muốn. Hãy thử mã sau:

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

Chỉnh sửa: Có một số câu hỏi là liệu việc đảo ngược thứ tự có tạo ra kết quả tương tự hay không. Vì vậy, tôi đã làm điều đó ( chính xác ) ở đây:

Như bạn có thể thấy, kết quả hoàn toàn giống nhau. Đây là bài kiểm tra ( đã sửa chữa ) ban đầu được tạo bởi ai đó có liên quan đến Drupal :

Và để có biện pháp tốt, đây là một hàm / phương thức bạn có thể sao chép và dán (được thử nghiệm trong 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
CƯỜI LỚN! Có thật không? Tôi đã bị bỏ phiếu cho tối ưu hóa "hơn"? Trong thực tế, tuần tự hóa của PHP chậm hơn đáng kể. Tôi sẽ cập nhật câu trả lời của tôi với bằng chứng ...
Nathan JB

19
Những gì Nathan đã làm ở đây đều có giá trị ngay cả khi người ta không thể thấy được giá trị của nó. Nó có thể là một sự tối ưu hóa có giá trị trong một số tình huống nằm ngoài ngữ cảnh của chúng tôi. Micro tối ưu hóa là một quyết định sai lầm trong một số nhưng không phải tất cả các tình huống
SeanDowney

13
Tôi không phải là người tối ưu hóa vi mô vì lợi ích của nó, nhưng nơi có sự gia tăng hiệu suất được ghi nhận mà không cần làm thêm, vậy tại sao không sử dụng nó.
hộp đệm

2
Trên thực tế, có vẻ như nó phụ thuộc vào độ sâu của mảng. Tôi tình cờ cần một thứ gì đó cần chạy càng nhanh càng tốt và trong khi POC của bạn cho thấy rằng json_encode () nhanh hơn ~ 300%, khi tôi thay đổi biến $ array trong mã của bạn thành use-case của tôi, nó đã trả về 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)(tính toán trước rõ ràng là không chính xác). Mảng của tôi sâu đến 2 cấp, vì vậy hãy nhớ rằng (như thường lệ) số tiền của bạn có thể khác nhau.
samitny

3
Được rồi, tôi không hiểu tại sao câu trả lời của Nathan không phải là câu trả lời hàng đầu. Nghiêm túc, sử dụng serialize và làm phiền người dùng của bạn với một trang web quá chậm. Sử thi +1 @ NathanJ.Brauer!
ReSpawN

168
md5(serialize($array));

13
nếu vì lý do nào đó bạn muốn khớp với hàm băm (dấu vân tay), bạn có thể muốn xem xét sắp xếp mảng "sort" hoặc "ksort", cũng có thể cần triển khai thêm một số loại chà / làm sạch
farinspace

9
Serialize chậm hơn nhiều so với json_encode từ câu trả lời thứ hai. Làm máy chủ của bạn vui vẻ và sử dụng json_encode! :)
s3m3n

3
Có vẻ như bạn cần phải chuẩn mảng của riêng mình để tìm ra liệu bạn nên sử dụng json_encode hay serialize. Tùy thuộc vào mảng mà nó khác nhau.
Ligemer

Tôi tin rằng đó là một cách sai, vui lòng kiểm tra giải thích của tôi bên dưới.
TermiT

1
@joelpittet - Không. Cả hai ví dụ trong liên kết drupal đó đều có lỗi. Xem các ý kiến ​​trong câu trả lời của tôi dưới đây. ;) Ví dụ: dl.dropboxusercontent.com/u/4115701/Screenshots/…
Nathan JB

26

Tôi đang tham gia một bữa tiệc rất đông người bằng cách trả lời, nhưng có một điều quan trọng mà không có câu trả lời hiện tại nào giải quyết. Giá trị của json_encode()serialize()cả hai phụ thuộc vào thứ tự của các phần tử trong mảng!

Dưới đây là kết quả của việc không sắp xếp và sắp xếp các mảng, trên hai mảng có các giá trị giống nhau nhưng được thêm vào theo một thứ tự khác nhau (mã ở cuối bài đăng) :

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

Do đó, hai phương pháp mà tôi muốn giới thiệu để băm một mảng sẽ là:

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

Sự lựa chọn json_encode()hoặc serialize()nên được xác định bằng cách thử nghiệm loại dữ liệu mà bạn đang sử dụng . Bằng thử nghiệm của riêng tôi trên dữ liệu số và văn bản thuần túy, nếu mã không chạy một vòng lặp chặt chẽ hàng nghìn lần thì sự khác biệt thậm chí không đáng để làm điểm chuẩn. Cá nhân tôi sử dụng json_encode()cho loại dữ liệu đó.

Đây là mã được sử dụng để tạo kiểm tra sắp xếp ở trên:

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

Triển khai deep_ksort () nhanh chóng của tôi, phù hợp với trường hợp này nhưng hãy kiểm tra nó trước khi sử dụng cho các dự án của riêng bạn:

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

Câu trả lời phụ thuộc nhiều vào kiểu dữ liệu của giá trị mảng. Đối với chuỗi lớn, sử dụng:

md5(serialize($array));

Đối với chuỗi ngắn và số nguyên, sử dụng:

md5(json_encode($array));

4 hàm PHP tích hợp sẵn có thể chuyển đổi mảng thành chuỗi: serialize () , json_encode () , var_export () , print_r () .

Lưu ý: hàm json_encode () chạy chậm trong khi xử lý mảng liên kết với các chuỗi là giá trị. Trong trường hợp này, hãy xem xét sử dụng hàm serialize () .

Kết quả kiểm tra mảng đa chiều với md5-băm (32 ký tự) trong các khóa và giá trị:

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%

Kết quả kiểm tra mảng đa chiều số:

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%

Nguồn kiểm tra mảng liên kết . Nguồn kiểm tra mảng số .


Bạn có thể vui lòng giải thích những gì là dây lớnngắn ?
AL

1
Chuỗi ngắn @AL - chuỗi chứa ít hơn 25-30 ký tự. chuỗi lớn - tất cả chứa hơn 25-30 ký tự.
Alexander Yancharuk

7

Ngoài câu trả lời tuyệt vời của Brock (+1), bất kỳ thư viện băm phù hợp nào đều cho phép bạn cập nhật băm theo từng bước, vì vậy bạn sẽ có thể cập nhật tuần tự từng chuỗi, thay vì phải xây dựng một chuỗi khổng lồ.

Xem: hash_update


cần lưu ý rằng phương pháp này không hiệu quả nếu bạn đang cập nhật với các mảnh nhỏ; Tuy nhiên, nó rất tốt cho nhiều tệp lớn.
wrygiel

@wrygiel Điều đó không đúng. Đối với MD5, việc nén luôn được thực hiện trong các khối 64 byte (bất kể kích thước "khối lớn" của bạn là bao nhiêu) và nếu bạn chưa lấp đầy một khối, sẽ không có quá trình xử lý nào xảy ra cho đến khi khối được lấp đầy. (Khi bạn hoàn tất hàm băm, khối cuối cùng được đệm lên thành một khối đầy đủ, như một phần của quá trình xử lý cuối cùng.) Để biết thêm thông tin cơ bản, hãy đọc cấu trúc Merkle-Damgard (mà MD5, SHA-1 và SHA-2 đều dựa trên ).
Chris Jester-Young

Bạn đúng. Tôi hoàn toàn bị đánh lừa bởi một bình luận trên một số trang web khác.
wrygiel

@wrygiel Đó là lý do tại sao bạn phải tự nghiên cứu khi làm theo một ý tưởng "tìm thấy trên Internet". ;-) Nói như vậy, nhận xét cuối cùng đó rất dễ viết đối với tôi, bởi vì tôi thực sự đã triển khai MD5 từ đầu cách đây vài năm (để thực hành kỹ năng lập trình Đề án của mình), vì vậy tôi biết rất rõ hoạt động của nó.
Chris Jester-Young

Đây chính xác là những gì tôi muốn. Đôi khi không thể chấp nhận việc di chuyển và sao chép một lượng lớn dữ liệu trong bộ nhớ. Vì vậy, giống như các câu trả lời khác, sử dụng serialize () là một ý tưởng rất tồi về mặt hiệu suất. Nhưng API này vẫn bị thiếu nếu tôi chỉ muốn băm một phần của Chuỗi từ một điểm bù nhất định.
Jianwu Chen

4
md5(serialize($array));

Sẽ hoạt động, nhưng hàm băm sẽ thay đổi tùy thuộc vào thứ tự của mảng (mặc dù điều đó có thể không quan trọng).


3

Lưu ý rằng serializejson_encodehành động khác khi nói đến mảng số trong đó các khóa không bắt đầu từ 0 hoặc các mảng kết hợp. json_encodesẽ lưu trữ các mảng như vậy dưới dạng an Object, do đó json_decodetrả về an Object, nơi unserializesẽ trả về một mảng có các khóa giống hệt nhau.


3

Tôi nghĩ rằng đây có thể là một mẹo hay:

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

Lưu ý quan trọng về serialize()

Tôi không khuyên bạn nên sử dụng nó như một phần của hàm băm vì nó có thể trả về kết quả khác cho các ví dụ sau. Kiểm tra ví dụ bên dưới:

Ví dụ đơn giản:

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

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

Sản xuất

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

Nhưng đoạn mã sau:

<?php

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

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

Đầu ra:

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

Vì vậy, thay vì đối tượng thứ hai php chỉ cần tạo liên kết "r: 2;" đối với trường hợp đầu tiên. Đó chắc chắn là cách tốt và đúng để tuần tự hóa dữ liệu, nhưng nó có thể dẫn đến các vấn đề với hàm băm của bạn.


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

có một số câu trả lời yêu cầu sử dụng json_code,

nhưng json_encode không hoạt động tốt với chuỗi iso-8859-1, ngay khi có một ký tự đặc biệt, chuỗi sẽ bị cắt.

tôi muốn lời khuyên để sử dụng var_export:

md5(var_export($array, true))

không chậm như serialize, không bị lỗi như json_encode


Không quá nhanh, lựa chọn tốt nhất là sử dụng MD4, var_export cũng chậm
user956584

0

Hiện tại, câu trả lời được bình chọn nhiều nhất md5(serialize($array));không hoạt động tốt với các đối tượng.

Xem xét mã:

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

Mặc dù các mảng khác nhau (chúng chứa các đối tượng khác nhau), chúng có cùng một hàm băm khi sử dụng md5(serialize($array));. Vì vậy, băm của bạn là vô ích!

Để tránh vấn đề đó, bạn có thể thay thế các đối tượng bằng kết quả của spl_object_hash()trước khi tuần tự hóa. Bạn cũng nên làm điều đó một cách đệ quy nếu mảng của bạn có nhiều cấp.

Mã dưới đây cũng sắp xếp các mảng theo khóa, như dotancohen đã đề xuất.

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

Bây giờ bạn có thể sử dụng md5(serialize(replaceObjectsWithHashes($array))).

(Lưu ý rằng mảng trong PHP là kiểu giá trị. Vì vậy, replaceObjectsWithHasheshàm KHÔNG thay đổi mảng ban đầu.)


0

Tôi không thấy giải pháp ở trên dễ dàng như vậy nên tôi muốn đóng góp một câu trả lời đơn giản hơn. Đối với tôi, tôi đã nhận được cùng một khóa cho đến khi tôi sử dụng ksort (sắp xếp khóa):

Đầu tiên được sắp xếp với Ksort, sau đó thực hiện sha1 trên json_encode:

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

thí dụ:

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

Đầu ra của mảng và hàm băm đã thay đổi:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.