1. Einleitung
Dieser Artikel beschäftigt sich damit, wie festgestellt werden kann, ob ein bestimmtes Array leer ist. Vorgestellt werden folgende Methoden:
- Vergleich mit array() per Identitätsoperator (===)
- Anwendung von count($arr)
- Anwendung von isset($arr[0]) (auch gepaart mit array_keys() und array_values())
- Anwendung von empty($arr)
- Anwendung von key($arr)
- Anwendung von current($arr)
- Anwendung von end($arr)
- Anwendung von each($arr)
- Anwendung von array_shift($arr)
- Anwendung von array_pop($arr)
Weitere Methoden unter Verwendung der existierenden array-Funktionen wären denkbar, werden hier aber ausgelassen, da sie entweder sehr umfangreich sind und/oder offensichtlich miserable Performance zeigen.
Empfohlene Methode: Wann immer möglich sollten empty() oder isset() verwendet werden. Falls diese nicht verwendet werden können, sollte auf den Vergleich mit array() per „===” (Identitätsoperator) zurückgegriffen werden.
<?php var_dump(empty($arr)); // gibt true aus, wenn $arr leer ist ?>
2. Identitätsoperator (===)
Der Identitätsoperator prüft, ob zwei Variablen exakt identisch sind. Bei Anwendung auf $a und $b ergibt $a === $b also dann true, wenn $a und $b den selben Datentyp und zusätzlich den selben Wert haben. Entsprechend kann über den Identitätsoperator herausgefunden werden, ob ein Array $arr leer ist, indem wiederum mit einem leeren Array verglichen wird: $arr === array().
Zur Demonstration des Operators dient das nächste Beispiel. Es werden drei Arrays definiert:
- $notEmptyNumberKeys: Ein Array, welches mehrere Werte mit fortlaufenden Integer-Schlüsseln (0, 1, 2, ...) enthält.
- $notEmptyStringKeys: Ein Array, welches mehreren Werte mit String-Schlüsseln enthält.
- $isEmpty: Ein leeres Array.
Diese werden nacheinander mit leeren Arrays per Identitätsoperator verglichen. Entsprechend der Erwartungen erhält man nur für $isEmpty das Ergebnis true, da diese Variable ein leeres Array enthält und somit mit einem anderen leeren Array identisch ist.
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); var_dump($notEmptyNumberKeys === array()); // false var_dump($notEmptyStringKeys === array()); // false var_dump($isEmpty === array()); // true ?>
bool(false) bool(false) bool(true)
Um sicherzustellen, dass PHP bei Anwendung des Identitätsoperators auch wirklich nur leere Arrays mit anderen leeren Arrays als identisch betrachtet, werden im nächsten Beispiel mehrere Testwerte erzeugt und nacheinander mit array() per Identitätsoperator verglichen. Man erhält dabei als Ergebnis, dass tatsächlich nur ein leeres Array mit einem anderen leeren Array als identisch angesehen wird. (Es gibt also keine bösen Überraschungen hier.)
<?php $testvalues = array( 'array()' => array(), 'array(null)' => array(null), 'array(\'\')' => array(''=>null), 'array(\'a\'=>null)' => array('a'=>null), 'array(false)' => array(false), 'array(0)' => array(0), 'array(1, 2, 3)' => array(1, 2, 3), 'null' => null, 'false' =>false, 'true' => true, 'test' => 'test', '0' => 0, '1' => 1, '0.001' => 0.001 ); $emptyArr = array(); foreach ($testvalues as $key=>$val) { echo "$key: " . ($val === $emptyArr ? 'true' : 'false') . "\n"; } ?>
array(): true array(null): false array(''): false array('a'=>null): false array(false): false array(0): false array(1, 2, 3): false null: false false: false true: false test: false 0: false 1: false 0.001: false
3. count()
Eine andere Variante, um zu prüfen, ob ein Array leer ist, stellt der Vergleich mit dem Ergebnis von count() dar. count($arr) gibt die Anzahl der Elemente im Array $arr zurück. Ist diese gleich null (bzw. kleiner als eins), ist das Array entsprechend leer.
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); var_dump(count($notEmptyNumberKeys) < 1); // false var_dump(count($notEmptyStringKeys) < 1); // false var_dump(count($isEmpty) < 1); // true ?>
bool(false) bool(false) bool(true)
4. isset()
Sofern bekannt ist, dass im Array immer dann ein Element mit Schlüssel 0 existiert, wenn das Array nicht leer ist, kann isset() verwendet werden. Die Schreibweise lautet in diesem Fall isset($arr[0]), um das Array $arr zu prüfen. Bei allen Arrays, die fortlaufen durchnummeriert sind, beginnend bei 0, kann isset() ohne Probleme angewendet werden. Das gilt für alle Arrays bei deren Definition keine Schlüssel explizit angegeben werden. PHP startet dann automatisch bei 0 und erhöht den Schlüssel mit jedem hinzugefügten Element um 1. (Natürlich lässt sich isset() in diesem Kontexkt auch auf andere Schlüssel des Arrays anwenden, sofern für diese ebenfalls gilt, dass das Array immer leer ist, wenn diese nicht definiert sind.)
<?php $notEmptyNumberKeys = array(1, 2, 3); // auf dieses Array sollte isset() nicht angewendet werden, da String-Schlüssel benutzt werden // allerdings könnte man isset($notEmptyStringKeys['I']) schreiben, wenn das Array immer mit "I" beginnt. $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); var_dump(!isset($notEmptyNumberKeys[0])); // false var_dump(!isset($notEmptyStringKeys[0])); // true, da Schlüssel 0 nicht existiert var_dump(!isset($isEmpty[0])); // true ?>
bool(false) bool(true) bool(true)
4.1. isset() und array_values()
Um die Beschränkung loszuwerden, dass die Arrays immer mit bestimmten Schlüsseln beginnen müssen (in der Regel 0), kann array_values() verwendet werden. Wird array_values($arr) auf ein Array $arr angewendet, dann gibt diese Funktion alle Werte als neues Array zurück. Die ursprünglichen Schlüssel gehen dabei verloren, die neuen beginnen wiederum bei 0 und sind fortlaufend durchnummeriert (0, 1, 2, 3, ...). (Es werden also sozusagen die Schlüssel des Arrays neu gebildet.)
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); // Neue Arrays bilden, die immer den Schlüssel 0 haben, wenn sie nicht leer sind $valuesNotEmptyNumberKeys = array_values($notEmptyNumberKeys); $valuesNotEmptyStringKeys = array_values($notEmptyStringKeys); $valuesIsEmpty = array_values($isEmpty); var_dump(!isset($valuesNotEmptyNumberKeys[0])); // false var_dump(!isset($valuesNotEmptyStringKeys[0])); // false var_dump(!isset($valuesIsEmpty[0])); // true ?>
bool(false) bool(false) bool(true)
4.2. isset() und array_keys()
array_keys() ist das Äquivalent von array_values() bezogen auf die Schlüssel eines Arrays. Die Funktion gibt also ein neues Array zurück, welches die Schlüssel des ursprünglichen Arrays als Werte enthält. Diese sind — wie bei array_values() — fortlaufend durchnummeriert, beginnend bei 0. Die Entscheidung, ob ein Array nun leer ist oder nicht, erfolgt wie bei array_values() über isset($ergebnis[0]). Da nur bekannt sein muss, ob das erste Element existiert, können sowohl die Werte (array_values()) als auch die Schlüssel (array_keys()) geprüft werden. (Genau genommen sind die Schlüssel sogar besser, da das erste Element den Wert NULL haben könnte und dieser Wert für isset() als nicht definiert gilt, wodurch isset() false zurückgibt.)
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); // Neue Arrays bilden, die immer den Schlüssel 0 haben, wenn sie nicht leer sind $keysNotEmptyNumberKeys = array_keys($notEmptyNumberKeys); $keysNotEmptyStringKeys = array_keys($notEmptyStringKeys); $keysIsEmpty = array_keys($isEmpty); var_dump(!isset($keysNotEmptyNumberKeys[0])); // false var_dump(!isset($keysNotEmptyStringKeys[0])); // false var_dump(!isset($keysIsEmpty[0])); // true ?>
bool(false) bool(false) bool(true)
5. empty()
empty($var) wird auf eine Variable angewendet und gibt true zurück, wenn diese als leer gilt. Wird es in Zusammenhang mit einem Array verwendet, so ist das Ergebnis immer dann true, wenn das Array keine Elemente enthält.
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); var_dump(empty($notEmptyNumberKeys)); // false var_dump(empty($notEmptyStringKeys)); // false var_dump(empty($isEmpty)); // true ?>
bool(false) bool(false) bool(true)
6. key()
key($arr) wird auf ein Array $arr angewendet und gibt NULL zurück, wenn der interne Array-Zeiger dieses Arrays auf kein Element im Array zeigt. Wurde der Array-Zeiger zuvor mit reset($arr) auf den Anfang des Arrays (bzw. auf das erste Element) zurückgesetzt und gibt key() bei sofort darauffolgender Anwendung NULL zurück, dann ist das Array entsprechend leer.
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); reset($notEmptyNumberKeys); reset($notEmptyStringKeys); reset($isEmpty); var_dump(key($notEmptyNumberKeys)===null); // false var_dump(key($notEmptyStringKeys)===null); // false var_dump(key($isEmpty)===null); // true ?>
bool(false) bool(false) bool(true)
7. current()
current($arr) wird genauso wie key() auf das Array $arr angewendet und gibt (bool)false zurück, wenn der interne Array-Zeiger auf kein Element im Array zeigt. Wieder muss der Array-Zeiger vor Anwendung über reset() zurückgesetzt werden — nur dann kann man sich sicher sein, dass dieser auf das erste Element zeigt (falls es denn existiert). Zu bedenken ist, dass current($arr) sowohl am Ende des Arrays false zurückgibt, als auch wenn der aktuelle Wert false ist. Daher sollte es nicht auf Arrays angewendet werden, die potentiell den Wert false enthalten können. (Oder besser: In diesem Kontext nur key() verwenden.)
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); reset($notEmptyNumberKeys); reset($notEmptyStringKeys); reset($isEmpty); var_dump(current($notEmptyNumberKeys)===false); // false var_dump(current($notEmptyStringKeys)===false); // false var_dump(current($isEmpty)===false); // true ?>
bool(false) bool(false) bool(true)
8. end()
end($arr) setzt den internen Array-Zeiger auf das letzte Element im Array $arr und gibt dessen Wert zurück. Wie current() gibt auch end() immer dann false zurück, wenn das Array leer ist, was auch hier zu Problemen führt, wenn das Array false enthalten kann.
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); var_dump(end($notEmptyNumberKeys)===false); // false var_dump(end($notEmptyStringKeys)===false); // false var_dump(end($isEmpty)===false); // true ?>
bool(false) bool(false) bool(true)
9. each()
each($arr) liest das gegenwärtige Element (genauer: dessen Schlüssel und Wert) aus und gibt dieses zurück. Mit „gegenwärtig” ist hier dasjenige Element gemeint, auf das der interne Array-Zeiger gerade gerichtet ist. Nach der Anwendung von each() wird dieser um eins weiter gegen Ende des Arrays verschoben. Die Rückgabe von each() ist ein Array mit Aufbau array(0=>Schlüssel, 'key'=>Schlüssel, 1=>Wert, 'value'=>Wert). Dieses Array kann ignoriert werden, da hier sowieso nur relevant ist, ob das ursprüngliche Array $arr leer ist. Wie current() gibt auch each() false zurück, wenn es das Ende des Arrays $arr erreicht hat (was hier eindeutig ist, da each() sonst ein Array zurückgibt). Entsprechend muss nur getestet werden, ob die Rückgabe von each() false ist.
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); reset($notEmptyNumberKeys); reset($notEmptyStringKeys); reset($isEmpty); var_dump(each($notEmptyNumberKeys)===false); // false var_dump(each($notEmptyStringKeys)===false); // false var_dump(each($isEmpty)===false); // true ?>
bool(false) bool(false) bool(true)
10. array_shift()
array_shift(&$arr) liefert das erste Element im Array $arr und entfernt dieses gleichzeitig aus $arr. Entsprechend sollte vor Anwendung von array_shift() eine Kopie von $arr angelegt werden, sofern man das Array in seinem ursprünglichen Zustand weiterverwenden will. Ist das Array bei Anwendung von array_shift() leer, dann gibt die Funktion NULL zurück. Vergleichbar zu current() besteht auch hier eine Uneindeutigkeit, da das Array durchaus den Wert NULL an erster Stelle enthalten könnte.
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); $notEmptyNumberKeys2 = $notEmptyNumberKeys; $notEmptyStringKeys2 = $notEmptyStringKeys; $isEmpty2 = $isEmpty; var_dump(array_shift($notEmptyNumberKeys2)===null); // false var_dump(array_shift($notEmptyStringKeys2)===null); // false var_dump(array_shift($isEmpty2)===null); // true ?>
bool(false) bool(false) bool(true)
11. array_pop()
array_pop($arr) arbeitet exakt identisch zu array_shift(), mit dem einzigen Unterschied, dass das letzte und nicht das erste Element des Arrays zurückgegeben (und entfernt) wird. Auch hier sollte das Array vorher kopiert werden und ebenfalls ist die Rückgabe nicht eindeutig, da NULL sowohl den Wert NULL als auch das Signal für ein leeres Array darstellt.
<?php $notEmptyNumberKeys = array(1, 2, 3); $notEmptyStringKeys = array('I' => 1, 'II' => 2, 'III' => 3); $isEmpty = array(); $notEmptyNumberKeys2 = $notEmptyNumberKeys; $notEmptyStringKeys2 = $notEmptyStringKeys; $isEmpty2 = $isEmpty; var_dump(array_pop($notEmptyNumberKeys2)===null); // false var_dump(array_pop($notEmptyStringKeys2)===null); // false var_dump(array_pop($isEmpty2)===null); // true ?>
bool(false) bool(false) bool(true)
12. Performance
Nachfolgend wird ein Skript ausgearbeitet, welches die Performance der zuvor vorgestellten Methoden miteinander vergleicht. Es erstellt nacheinander 13 Arrays, welche eine steigende Anzahl von Werten enthalten. Die Anzahl ergibt sich aus 2x, wobei x der aktuellen Iteration (beginnend bei 1, endend bei 13) entspricht. Der Aufbau der Arrays ist immer identisch: 2x mal der Wert "x" (String). Für jedes dieser Arrays werden nun nacheinander die vorgestellten Methoden durchgetestet. Für jede Methode wiederum gibt es eine eigene Funktion in der diese jeweils 100.000 mal über eine for-Schleife auf das übergebene Array angewendet wird. Der Zeitbedarf wird dabei über microtime() geloggt.
Die Ergebnisse von array_shift() und array_pop() sind mit Vorsicht zu betrachten, da diese Funktionen die übergebenen Arrays mit jeder Iteration verkleinern und so in der Regel lange vor Ende der 100.000 Iterationen bereits mit leeren Arrays arbeiten. Leider lässt sich das kaum kompensieren, da das Ändern der Arrays durch array_shift()/array_pop() nicht deaktiviert werden kann. Nach jeder Anwendung der verwendeten Variable eine Kopie des ursprünglichen Arrays zuzuweisen führt nur dazu, dass der Zeitbedarf durch das Kopieren um ein Vielfaches ansteigt.
Bei key(), current() und each() wird am Anfang jeder Iteration noch reset() ausgeführt, ausgehend davon, dass bei einer alltäglichen Anwendung der Funktionen nicht garantiert ist, dass der Array-Zeiger wirklich am Anfang des Arrays steht (oder zumindest auf irgendein Element zeigt). Ohne reset() dürfte die Messung für die drei Funktionen entsprechend besser ausfallen.
Abgesehen davon lauten die Ergebnisse, welche auch der Tabelle entnommen werden können, wie folgt:
- Alle Funktionen außer array_shift() zeigen einen konstanten Zeitbedarf pro Aufruf, unabhängig von der Arraygröße. Dies gilt insbesondere auch für count() — offenbar wird die Anzahl der Elemente in einem Array von PHP zwischengespeichert.
- empty() und isset() sind am schnellsten, mit minimalem Vorsprung für empty().
- Der Vergleich mit array() ($arr === array()) benötigt etwa doppelt so viel Zeit wie empty().
- count() benötigt etwa drei mal so viel Zeit wie empty(). Minimal langsamer ist end().
- key() und current() benötigen etwa fünf mal so viel Zeit wie empty().
- each benötigt etwa zehn bis elf mal so viel Zeit wie empty() und ist damit fast durchweg am langsamsten.
Elements | === array() | count | isset | empty | key | current | end | each | array_shift | array_pop |
---|---|---|---|---|---|---|---|---|---|---|
2 | 0.024 | 0.0306 | 0.0102 | 0.0095 | 0.0495 | 0.0586 | 0.0352 | 0.1072 | 0.0299 | 0.03 |
4 | 0.0221 | 0.0298 | 0.0103 | 0.0095 | 0.0502 | 0.0589 | 0.0349 | 0.1099 | 0.0295 | 0.0303 |
8 | 0.0219 | 0.0313 | 0.0104 | 0.0097 | 0.0503 | 0.0585 | 0.036 | 0.11 | 0.0303 | 0.0302 |
16 | 0.0229 | 0.03 | 0.0103 | 0.0095 | 0.05 | 0.0584 | 0.0348 | 0.1082 | 0.0301 | 0.0302 |
32 | 0.0215 | 0.0299 | 0.0104 | 0.0094 | 0.0501 | 0.0582 | 0.0353 | 0.1087 | 0.0296 | 0.0303 |
64 | 0.0219 | 0.0298 | 0.0106 | 0.0095 | 0.0511 | 0.0582 | 0.0352 | 0.1074 | 0.0298 | 0.0303 |
128 | 0.0214 | 0.0295 | 0.0103 | 0.0096 | 0.0503 | 0.0591 | 0.0355 | 0.11 | 0.0306 | 0.0304 |
256 | 0.0217 | 0.0301 | 0.0101 | 0.0095 | 0.0505 | 0.0597 | 0.0351 | 0.1088 | 0.0318 | 0.0306 |
512 | 0.0214 | 0.0301 | 0.0103 | 0.0096 | 0.0497 | 0.0587 | 0.0358 | 0.1071 | 0.0308 | 0.0303 |
1024 | 0.0221 | 0.0306 | 0.0103 | 0.0095 | 0.0504 | 0.0589 | 0.0356 | 0.1084 | 0.0393 | 0.0337 |
2048 | 0.0218 | 0.0295 | 0.0106 | 0.0097 | 0.0503 | 0.0601 | 0.0365 | 0.1096 | 0.0493 | 0.0304 |
4096 | 0.0217 | 0.0297 | 0.0106 | 0.0094 | 0.0518 | 0.0601 | 0.0375 | 0.1099 | 0.1183 | 0.0339 |
8192 | 0.022 | 0.0304 | 0.0104 | 0.0095 | 0.0525 | 0.0585 | 0.0356 | 0.1096 | 0.3442 | 0.0332 |
<?php const ITERATIONS = 100000; // === Operator function testIdentity($arr, $iterations) { $start = microtime(true); for ($x=0; $x<$iterations; $x++) { $arr === array(); } return round(microtime(true) - $start, 4); } // count function testCount($arr, $iterations) { $start = microtime(true); for ($x=0; $x<$iterations; $x++) { count($arr) < 1; } return round(microtime(true) - $start, 4); } // isset function testIsset($arr, $iterations) { $start = microtime(true); for ($x=0; $x<$iterations; $x++) { !isset($arr[0]); } return round(microtime(true) - $start, 4); } // empty function testEmpty($arr, $iterations) { $start = microtime(true); for ($x=0; $x<$iterations; $x++) { empty($arr) === true; } return round(microtime(true) - $start, 4); } // reset + key function testKey($arr, $iterations) { $start = microtime(true); for ($x=0; $x<$iterations; $x++) { reset($arr); key($arr) === null; } return round(microtime(true) - $start, 4); } // reset + current function testCurrent($arr, $iterations) { $start = microtime(true); for ($x=0; $x<$iterations; $x++) { reset($arr); current($arr) === false; } return round(microtime(true) - $start, 4); } // end function testEnd($arr, $iterations) { $start = microtime(true); for ($x=0; $x<$iterations; $x++) { end($arr) === false; } return round(microtime(true) - $start, 4); } // reset + each function testEach($arr, $iterations) { $start = microtime(true); for ($x=0; $x<$iterations; $x++) { reset($arr); each($arr) === false; } return round(microtime(true) - $start, 4); } // array_shift function testArrayShift($arr, $iterations) { $arr2 = $arr; $start = microtime(true); for ($x=0; $x<$iterations; $x++) { array_shift($arr) === null; //$arr = $arr2; } return round(microtime(true) - $start, 4); } // array_pop function testArrayPop($arr, $iterations) { $arr2 = $arr; $start = microtime(true); for ($x=0; $x<$iterations; $x++) { array_pop($arr) === null; //$arr = $arr2; } return round(microtime(true) - $start, 4); } ?> <table id="php-is-empty-performance"> <thead> <tr> <th>Elements</th> <th>=== array()</th> <th>count</th> <th>isset</th> <th>empty</th> <th>key</th> <th>current</th> <th>end</th> <th>each</th> <th>array_shift</th> <th>array_pop</th> < /tr> </thead> <tbody> <?php foreach (range(1, 13) as $key=>$val): ?> <?php $countValues = pow(2, $val); ?> <?php $arr = array_fill(0, $countValues, 'x'); ?> <tr> <td><?php echo count($arr); ?></td> <td><?php echo testIdentity($arr, ITERATIONS); ?></td> <td><?php echo testCount($arr, ITERATIONS); ?></td> <td><?php echo testIsset($arr, ITERATIONS); ?></td> <td><?php echo testEmpty($arr, ITERATIONS); ?></td> <td><?php echo testKey($arr, ITERATIONS); ?></td> <td><?php echo testCurrent($arr, ITERATIONS); ?></td> <td><?php echo testEnd($arr, ITERATIONS) ?></td> <td><?php echo testEach($arr, ITERATIONS); ?></td> <td><?php echo testArrayShift($arr, ITERATIONS); ?></td> <td><?php echo testArrayPop($arr, ITERATIONS); ?></td> </tr> <?php endforeach; ?> </tbody> </table>