この記事では、PHP 5 の foreach のコピー動作について説明します。 PHP の内部動作、つまり zvals、refcount、コピーオンライトの動作についての知識が必要です。
#PHP の foreach は、非常にきちんとした、要点を絞った言語構造です。それでも、遅いと考えて使いたくない人もいます。一般的な名前の理由の 1 つは、foreach が反復する配列をコピーするためです。
したがって、より直観的かつ直接的に書くのではなく、
$keys = array_keys($array); $size = count($array); for ($i = 0; $i < $size; $i++) { $key = $keys[$i]; $value = $array[$key]; // ... }
と書くことを提案する人もいます:
foreach ($array as $key => $value) { // ... }
ここには 2 つの問題があります:
マイクロ最適化は悪いです。多くの場合、それは時間を無駄にするだけで、目に見えるパフォーマンスの向上にはつながりません。
foreach のコピー動作は、ほとんどの人が考えているよりも少し複雑です。通常、「最適化された」バージョンは元のバージョンよりも遅くなります。
foreach はいつコピーされますか?
foreach が配列をコピーするかどうか、およびコピー数は 3 つのことに依存します:
反復配列が参照されるかどうか、それ refcount の高さ、および反復が参照によって行われるかどうか。
No Reference, refcount == 1
以下のコードでは、$array は参照されず、refcount は 1 です。この場合、foreach は配列をコピーしません (証明)。これは、foreach が参照なしで反復された配列を常にコピーするという一般的な考えに反してです。
test(); function test() { $array = range(0, 100000); foreach ($array as $key => $value) { // ... } }
理由は簡単です: なぜこれを行うのですか? foreach が $array を変更する唯一の場所は、それが内部配列ポインターであるためです。これは予期された動作であるため、予防する必要はありません。
未参照、refcount > 1
以下のコードは、前のコードとよく似ています。唯一の違いは、配列がパラメーターとして渡されることです。これは重要ではないように思えるかもしれませんが、foreach の動作が変わります。
これで、値ではなく配列構造がコピーされます (証拠。これが単にコピーされた構造であると疑問に思う場合は、比較してください)このスクリプトとそのスクリプト。最初のスクリプトは構造のみをコピーし、2 番目のスクリプトは両方をコピーします)。
$array = range(0, 100000); test($array); function test($array) { foreach ($array as $key => $value) { // ... } }
これは一見すると少し奇妙に思えるかもしれません:
配列がパラメータとして渡された場合はコピーされるのに、関数で定義されている場合はコピーされないのはなぜですか? その理由は次のとおりです。配列 zval は複数の変数間で共有されるようになりました。関数の外側の $array 変数と関数内の $array 変数です。 foreach が配列構造をコピーせずに配列を反復すると、関数内の $array 変数の配列ポインタだけでなく、関数外の $array 変数のポインタも変更されます。したがって、foreach は配列構造 (つまり、ハッシュ テーブル) をコピーする必要があります。一方、値は引き続き zval を共有できるため、コピーする必要はありません。
引用
次の状況は、前の状況と非常によく似ています。唯一の違いは、配列が参照によって渡されることです。この場合、配列はコピーされません (証明)。
$array = range(0, 100000); test($array); function test(&$array) { foreach ($array as $key => $value) { // ... } }
この場合、同じ理由が前のケースに当てはまります。つまり、外側の $array と内側の $array が zval を共有します。違いは、それらが参照 (isref == 1) であることです。そのため、この場合、内側の配列に対する変更は外側の配列にも適用されます。したがって、内側の配列の配列ポインタが変更されると、外側の配列の配列ポインタも変更される必要があります。 foreach がコピーを必要としないのはこのためです。
参照による反復
#上記の例はすべて、値によって反復されます。参照の反復の場合も同じルールが適用されますが、値の参照を追加すると、配列値のコピーの動作が変更されます (構造のコピーに関する動作は同じままです)。 「未参照、refcount == 1」の場合は変更されていません。参照反復とは、$ 値に変更があった場合に、配列がコピーされないように元の配列を変更することを意味します (証明)。 「参照される」場合も同じままです。この場合、$value を変更すると、反復配列を参照するすべての変数が変更されます (証明)。 「未参照、refcount > 1」の場合のみ変更されています。これは、配列構造とその値をコピーする必要があるためです。そうしないと、関数の外側の $array 変数の配列ポインタが変更され、$value を変更すると、外側の $array 値も変更されてしまうためです (証明)。概要
反復配列が参照されておらず、refcount > 1 である場合に限り、foreach は配列構造をコピーします。foreach はまた、前の点が適用され、反復が参照によって行われる場合に限り、配列値をコピーします以上がPHP の foreach コピーはいつ行われますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。