MD5多次元配列へのPHPの最良の方法?


120

多次元配列のMD5(またはその他のハッシュ)を生成する最良の方法は何ですか?

配列の各レベルをトラバースし、各値を文字列に連結して、単純に文字列に対してMD5を実行するループを簡単に作成できます。

しかし、これはせいぜい扱いにくいように思われ、多次元配列を取得してハッシュ化するファンキーな関数があるかどうか疑問に思いました。

回答:


260

(下部にあるコピーアンドペースト可能な機能)

前に述べたように、以下が機能します。

md5(serialize($array));

ただし、(皮肉なことに)json_encodeは著しく高速であることに注意してください。

md5(json_encode($array));

実際、(1)json_encodeのみがシリアライズよりも高速に実行され、(2)json_encodeが生成する文字列が小さくなるため、md5が処理する速度が低下するため、ここでは速度が2倍になります。

編集:これはこの主張を支持する証拠です:

<?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は一貫して250%(2.5x)以上高速です(多くの場合300%以上)-これは些細な違いではありません。このライブスクリプトを使用したテストの結果は、次の場所に表示されます。

ここで、注目すべき点の1つは、array(1,2,3)がarray(3,2,1)とは異なるMD5を生成することです。 これがあなたが望むものでない場合。次のコードを試してください:

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

編集:順序を逆にしても同じ結果が得られるかどうかについて、いくつかの質問がありました。だから、私はそれをここで(正しく)行いました:

ご覧のとおり、結果はまったく同じです。Drupalに関連する誰かが最初に作成した(修正された)テストは次のとおりです。

そして良い尺度として、ここにコピーして貼り付けることができる関数/メソッドがあります(5.3.3-1-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
笑!本当に?「オーバー」最適化に反対票を投じましたか?実際には、PHPのシリアライズは大幅に遅くなります。回答を証拠で更新します...
ネイサンJB

19
ネイサンがここで行ったことは、たとえその価値が見えなくても貴重です。これは、状況に合わない状況では価値のある最適化になる可能性があります。マイクロ最適化は、すべてではないが一部の状況では不適切な決定です
SeanDowney

13
私はそれのためのマイクロ最適化の1つではありませんが、余分な作業がないためにパフォーマンスの向上が文書化されている場合は、なぜそれを使用しないのですか?
バンパーボックス

2
実際には、配列の深さに依存するようです。可能な限り高速に実行する必要があるものが必要になり、POCはjson_encode()が約300%高速であることを示していますが、コードの$ array変数をユースケースに変更すると、それが返されました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)(パーセント計算明らかに不正確です)。私の配列は最大2レベルの深さなので、(いつものように)走行距離が異なる場合があることに注意してください。
samitny

3
わかりました、なぜネイサンの答えがトップの答えではないのかわかりません。真剣に、非常に遅いサイトでユーザーをシリアライズしてイライラさせてください。エピック+1 @ NathanJ.Brauer!
ReSpawN 2014年

168
md5(serialize($array));

13
何らかの理由でハッシュ(指紋)を一致させたい場合は、配列 "sort"または "ksort"の並べ替えを検討する必要があります。さらに、何らかのスクラブ/クリーニングの実装も必要になる場合があります
farinspace

9
Serializeは、2番目の答えからのjson_encodeよりもかなり遅いです。サーバーに喜びを感じ、json_encodeを使用してください!:)
s3m3n 2013

3
json_encodeを使用する必要があるか、シリアライズする必要があるかを判断するには、独自の配列をベンチマークする必要があるようです。アレイによって異なります。
リゲマー2014年

間違った方法だと思います。以下の説明を確認してください。
TermiT 2014年

1
@joelpittet-いいえ。そのdrupalリンクの両方の例にはバグがあります。以下の私の回答のコメントを参照してください。;)、例えばdl.dropboxusercontent.com/u/4115701/Screenshots/...
ネイサンJB

26

私は非常に混雑したパーティーに答えることによって参加していますが、現存する答えのどれも対処しないという重要な考慮事項があります。およびの値は、配列内の要素の順序に依存します!json_encode()serialize()

これは、配列をソートおよびソートしない、同じ値を持つが異なる順序で追加された2つの配列 の結果です(ポストの下部にあるコード)

    serialize()
1c4f1064ab79e4722f41ab5a8141b210
1ad0f2c7e690c8e3cd5c34f7c9b8573a

    json_encode()
db7178ba34f9271bfca3a05c5dddf502
c9661c0852c2bd0e26ef7951b4ca9e6f

    Sorted serialize()
1c4f1064ab79e4722f41ab5a8141b210
1c4f1064ab79e4722f41ab5a8141b210

    Sorted json_encode()
db7178ba34f9271bfca3a05c5dddf502
db7178ba34f9271bfca3a05c5dddf502

したがって、配列ハッシュするために推奨する2つの方法は次のとおりです。

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

json_encode()またはの選択は、使用いるデータのタイプをテストすることによって決定するserialize()必要があります。純粋にテキストと数値のデータを自分でテストした結果、コードが何千回もタイトループを実行していない場合、その差はベンチマークの価値さえありません。私は個人的にその種類のデータを使用しています。json_encode()

上記の並べ替えテストを生成するために使用されるコードは次のとおりです。

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

私の簡単なdeep_ksort()実装はこのケースに適合しますが、独自のプロジェクトで使用する前に確認してください:

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

答えは、配列値のデータ型に大きく依存します。大きな文字列の場合:

md5(serialize($array));

短い文字列と整数を使用する場合:

md5(json_encode($array));

4つの組み込みPHP関数は、配列を文字列に変換できます: serialize()json_encode()var_export()print_r()

注意: json_encode()関数は、文字列を値として持つ連想配列の処理中に速度が低下します。この場合は、serialize()関数の使用を検討してください。

キーと値にmd5ハッシュ(32文字)を含む多次元配列のテスト結果:

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%

数値の多次元配列のテスト結果:

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%

連想配列テストソース。数値配列テストソース


あなたは何かを説明してくださいすることができ、大きな短い文字列
ALは

1
@AL 短い文字列 -25〜30 文字未満の文字列。大きな文字列 -すべてが25〜30文字を超えています。
Alexander Yancharuk、2015年

7

Brockの優れた回答(+1)は別として、適切なハッシュライブラリを使用すると、ハッシュを段階的に更新できるため、1つの巨大な文字列を作成する代わりに、各文字列を順番に更新できるはずです。

見る: hash_update


小さなフラグメントで更新している場合、この方法は非効率的です。ただし、巨大なファイルの大きなチャンクには適しています。
wrygiel 2012

@wrygielそれは本当ではない。MD5の場合、圧縮は常に64バイトブロックで行われ(「大きなチャンク」のサイズに関係なく)、まだブロックがいっぱいになっていない場合は、ブロックがいっぱいになるまで処理は行われません。(ハッシュをファイナライズすると、最終処理の一部として、最後のブロックが完全なブロックになるまで埋め込まれます。)より多くの背景については、Merkle-Damgard構成を読んでください(MD5、SHA-1、およびSHA-2はすべて、 )。
Chris Jester-Young

あなたが正しい。他のサイトのコメントに完全に惑わされました。
wrygiel 2012

@wrygielそのため、「インターネット上で発見された」というアイデアに従うときに、独自の調査を行うことにはお金がかかります。;-)言っておくと、実際にMD5を最初から実装して数年前に(私のSchemeプログラミングスキルを練習するため)、最後のコメントを書くのは簡単だったので、その動作をよく知っています。
Chris Jester-Young

これがまさに私が欲しいものです。メモリ内のデータの大きなトラックを移動してコピーすることは、許容できない場合があります。したがって、serialize()を使用する他の回答と同様に、パフォーマンスの点で非常に悪い考えです。しかし、特定のオフセットから文字列の一部のみをハッシュしたい場合、このAPIはまだありません。
Jianwu Chen

4
md5(serialize($array));

動作しますが、ハッシュは配列の順序に応じて変化します(ただし、それは重要ではない場合があります)。


3

キーが0で始まらない数値配列、または連想配列の場合はserializejson_encode動作が異なることに注意してください。 json_encodeこのような配列を格納するObjectので、json_decode戻りObject場合、unserialize全く同じキーを持つ配列を返します。


3

これは良いヒントになると思います:

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

についての重要な注意 serialize()

次の例では異なる結果を返す可能性があるため、ハッシュ関数の一部として使用することはお勧めしません。以下の例を確認してください:

簡単な例:

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

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

生産する

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

しかし、次のコード:

<?php

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

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

出力:

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

したがって、2番目のオブジェクトの代わりに、phpはリンク "r:2;"を作成します。最初のインスタンスに。これは間違いなくデータをシリアル化するための正しい方法ですが、ハッシュ関数で問題が発生する可能性があります。


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を使用するように指示するいくつかの回答があります。

しかし、json_encodeはiso-8859-1文字列では正常に機能しません。特別な文字があるとすぐに、文字列が切り取られます。

私はvar_exportを使用することをお勧めします:

md5(var_export($array, true))

シリアライズほど遅くなく、json_encodeほどバグがありません


それほど速くはないが、最良のオプションはmd4を使用することであり、var_exportも遅い
user956584

0

現在、最も多く投票されている回答md5(serialize($array));はオブジェクトではうまく機能しません。

コードを検討してください:

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

配列は異なります(それらには異なるオブジェクトが含まれます)でも、を使用すると、ハッシュは同じになりmd5(serialize($array));ます。だからあなたのハッシュは役に立たない!

その問題を回避するために、spl_object_hash()シリアル化する前の結果でオブジェクトを置き換えることができます。配列に複数のレベルがある場合も、再帰的に行う必要があります。

以下のコードは、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;
}

これでを使用できますmd5(serialize(replaceObjectsWithHashes($array)))

(PHPの配列は値型です。そのため、replaceObjectsWithHashes関数は元の配列を変更しないでください。)


0

上ではそれほど簡単に解決策を見つけられなかったので、より簡単な答えを提供したいと思いました。私にとっては、ksort(キーソート)を使用するまで、同じキーを取得していました。

最初にKsortで並べ替え、次にjson_encodeでsha1を実行します。

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

例:

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

変更された配列とハッシュの出力:

string(40) "502c2cbfbe62e47eb0fe96306ecb2e6c7e6d014c"
string(40) "b3319c58edadab3513832ceeb5d68bfce2fb3983"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.