php array_shift与array_pop执行速度差距为啥这么大

本文深入探讨了PHP中array_shift与array_pop函数在处理大型数组时的性能差异,揭示了array_shift操作导致数字键值重新排序的内部机制,以及由此产生的性能开销。

闲话不说,上问题:

一个很大的php数组(1w+),使用array_shfit跟array_pop取数组元素时,性能差距特别大,array_shift慢的无法忍受,而array_pop就很快了。

先不说答案,看段代码:

$arr = array(
    0=>123,
    3=>132,
    2=>987,
);
array_shift($arr);
//array_pop($arr);
var_dump($arr);

输出会有什么不同呢,

array_shift后,输出为:

array(2) {

  [0]=>

  int(132)

  [1]=>

  int(987)

}

array_pop后,输出为:

array(2) {

  [0]=>

  int(123)

  [3]=>

  int(132)

}

有什么不同?

对,就是array_shift操作后,数组的键值变了。这就是array_shift为什么慢的原因了。因为array_shift操作数组会对数字键值,重新从0开始重建。每次移出一个元素后,就得遍历数组中的所有元素。array_pop就不会了。一个是O(1),一个是O(n)的复杂度,数组大了之后,效果就很明显了。

php里对应函数的C代码实现:

/* {{{ void _phpi_pop(INTERNAL_FUNCTION_PARAMETERS, int off_the_end) */
static void _phpi_pop(INTERNAL_FUNCTION_PARAMETERS, int off_the_end)
{
	zval *stack,	/* Input stack */
		 **val;		/* Value to be popped */
	char *key = NULL;
	uint key_len = 0;
	ulong index;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &stack) == FAILURE) {
		return;
	}

	if (zend_hash_num_elements(Z_ARRVAL_P(stack)) == 0) {
		return;
	}

	/* Get the first or last value and copy it into the return value */
	if (off_the_end) {
		zend_hash_internal_pointer_end(Z_ARRVAL_P(stack));
	} else {
		zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
	}
	zend_hash_get_current_data(Z_ARRVAL_P(stack), (void **)&val);
	RETVAL_ZVAL(*val, 1, 0);

	/* Delete the first or last value */
	zend_hash_get_current_key_ex(Z_ARRVAL_P(stack), &key, &key_len, &index, 0, NULL);
	if (key && Z_ARRVAL_P(stack) == &EG(symbol_table)) {
		zend_delete_global_variable(key, key_len - 1 TSRMLS_CC);
	} else {
		zend_hash_del_key_or_index(Z_ARRVAL_P(stack), key, key_len, index, (key) ? HASH_DEL_KEY : HASH_DEL_INDEX);
	}
//就是下面这里,遍历所有元素,对是数字键的元素重新赋值。pop的off_the_end是1,shift的off_the_end是0
	/* If we did a shift... re-index like it did before */
	if (!off_the_end) {
		unsigned int k = 0;
		int should_rehash = 0;
		Bucket *p = Z_ARRVAL_P(stack)->pListHead;
		while (p != NULL) {
			if (p->nKeyLength == 0) {//键值是数字
				if (p->h != k) {
					p->h = k++;
					should_rehash = 1;
				} else {
					k++;
				}
			}
			p = p->pListNext;
		}
		Z_ARRVAL_P(stack)->nNextFreeElement = k;
		if (should_rehash) {
		//因为重新给键赋值,hash过后的位置可能不一样了,就得重新hash后,放到对应的位置。
			zend_hash_rehash(Z_ARRVAL_P(stack));
		}
	} else if (!key_len && index >= Z_ARRVAL_P(stack)->nNextFreeElement - 1) {
		Z_ARRVAL_P(stack)->nNextFreeElement = Z_ARRVAL_P(stack)->nNextFreeElement - 1;
	}

	zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
}

PHP_FUNCTION(array_pop)
{
	_phpi_pop(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
PHP_FUNCTION(array_shift)
{
	_phpi_pop(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}


转载于:https://my.oschina.net/u/437615/blog/297378

### PHP `array_pop` 和 `array_shift` 的区别及使用场景 #### 区别 1. **操作位置** - `array_pop()` 用于删除数组的最后一个元素,并返回该元素。它对数组的末尾进行操作[^2]。 - `array_shift()` 用于删除数组的第一个元素,并返回该元素。它对数组的开头进行操作[^4]。 2. **键名处理** - 使用 `array_pop()` 删除元素后,数组的键名不会重新编号,保持原有的键名结构。 - 使用 `array_shift()` 删除元素后,所有数字键名将被重新编号为从零开始计数,而字符串键名则保持不变。 3. **性能差异** - `array_pop()` 的性能较高,因为它只涉及数组末尾的操作,不需要移动其他元素[^2]。 - `array_shift()` 的性能较低,尤其是在型数组中,因为删除第一个元素后,所有后续元素都需要向前移动一位,这涉及到量的内存重分配操作[^4]。 #### 使用场景 - **`array_pop()` 的使用场景** 当需要从数组末尾移除元素时,例如实现一个栈(Stack)数据结构,可以使用 `array_pop()`。栈的特点是“后进先出”(LIFO),即最后添加的元素最先被移除。 - **`array_shift()` 的使用场景** 当需要从数组开头移除元素时,例如实现一个队列(Queue)数据结构,可以使用 `array_shift()`。队列的特点是“先进先出”(FIFO),即最早添加的元素最先被移除[^2]。 #### 示例代码 以下是两个函数的用法示例: ```php // array_pop 示例 $fruits = ["apple", "banana", "cherry"]; $lastFruit = array_pop($fruits); // 移除并返回 "cherry" print_r($fruits); // 输出 Array ( [0] => apple [1] => banana ) // array_shift 示例 $fruits = ["apple", "banana", "cherry"]; $firstFruit = array_shift($fruits); // 移除并返回 "apple" print_r($fruits); // 输出 Array ( [0] => banana [1] => cherry ) ``` #### 注意事项 - 如果数组为空或不是数组类型,`array_pop()` 和 `array_shift()` 都会返回 `NULL`[^4]。 - 在使用这些函数时,应确保数组的键名符合预期,尤其是当数组包含混合键名(数字和字符串)时[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值