PHP数组原理和高级应用

Jackey PHP 3,910 次浏览 , 1条评论

如何构造一个数组?

  • 通常做法:$items = array()
  • 也可以不初始化直接写:
    $item[0] = 'abc123';
    $item[] = 'abc123';
    items['name'] = 'andy';

我们还可以这样写:
$items = ['a', 'b', 'c'];

其实,对象也能当做数组来用。
$obj = new obj;
$obj[] = 'Append 1';
$obj[] = 'Append 2';
$obj[] = 'Append 3';
print_r($obj);

数组式的访问接口

提供像访问数组一样访问对象的能力的接口

  1. ArrayAccess{
  2. /*方法*/
  3. abstract public boolean offsetExists(mixed $offset)
  4. abstract public mixed offsetGet(mixed $offset)
  5. abstract public void offsetSet(mixed $offset, mixed $value)
  6. abstract public void offsetUnset(mixed $offset)
  7. }

 

ArrayAccess::offsetExists -- 检查一个偏移位置是否存在
ArrayAccess::offsetGet -- 获取一个偏移位置的值
ArrayAccess::offsetSet -- 设置一个偏移位置的值
ArrayAccess::offsetUnset -- 复位一个偏移位置的值

思考:
上面的四个接口方法分别有什么作用?在什么情况下会被调用?
如果不继承ArrayAccess接口,只是实现这四个方法还可以实现数组式访问的功能么?

例子:

  1. <?php
  2. class obj implements arrayaccess{
  3. private $container = array();
  4. public function __construct(){
  5. $this->container = array(
  6. "one" => 1,
  7. "two" => 2,
  8. "three" => 3,
  9. );
  10. }
  11. public function offsetSet($offset, $value){
  12. if(is_null($offset)){
  13. $this->container[] = $value;
  14. } else {
  15. $this->container[$offset] = $value;
  16. }
  17. }
  18. public function offsetExists($offset) {
  19. return isset($this->container[$offset]);
  20. }
  21. public function offsetUnset($offset) {
  22. unset($this->container[$offset]);
  23. }
  24. public function offsetGet($offset) {
  25. return isset($this->container[$offset]) ? $this->container[$offset] : null;
  26. }
  27. }
  28.  
  29. $obj = new obj;
  30. $obj[] = 'Append 1';
  31. $obj[] = 'Append 2';
  32. $obj[] = 'Append 3';
  33. print_r($obj);

 

数组key和value的限制条件

什么类型值可以当做数组的key,数组的value值又可以存什么类型?

  1. $array = [
  2. 1 => "a",
  3. "1" => "b",
  4. 1.5 => "c",
  5. true => "d",
  6. ];
  7. var_dump($array);
  8.  
  9. $array = [
  10. "foo" => "bar",
  11. "bar" => "foo",
  12. 100 => -100,
  13. -100 => 100,
  14. ];
  15. var_dump($array);

 

  • key可以使integer或者string
  • value可以使任意类型

key会有如下的强制转换:

  • 包含有合法整型值的字符串会被转换为整型
  • 浮点数和布尔值也会被转换为整型
  • 键名null实际会被存储为""
  • 数组和对象不能被用为键名
  • 相同键名,之前的会被覆盖

数组的访问

获取数组元素的值有哪些方法?
1.方括号,数组单元可以通过array[key]语法来访问
2.花括号也可以,例如$array[42] 和 $array{2} 效果相同。
3.方括号可以包括“表达式”,例如:$arr[somefunc($bar)];
4.自PHP5.4起可以用数组间接引用函数或方法调用的结果。

  1. function getArray() {
  2. return [1, 2, 3];
  3. }
  4.  
  5. $secondElement = getArray()[1];

 

数组元素的删除unset

unset()函数允许删除数组中的某个键。

  1. $array = [1, 2, 3, 4, 5];
  2. foreach($array as $key => $value) {
  3. unset($array[$key]);
  4. }
  5.  
  6. $array[] = 6;
  7. print_r($array);

注意:数组unset后,不会重建索引。

 

PHP数组类型与其他类型的转换

在PHP中,存在8中变量类型,分为三类

  • 标量类型:boolean、integer、float(double)、string
  • 复合类型:array,object
  • 特殊类型:resource、null

两个问题:
1.如何讲一个int、float、string、Boolean类型转换为数组?
2.var_dump((array)false)和var_dump((array)null)的输出结果是?

  • int,float,string,Boolean和resource类型(array)$scalarValue等同array($scalarValue)
  • object转换为array,结果为一个数组,其单元为该对象的属性,键名将为成员变量名
  • 将null转换为array会得到一个空的数组。
  1. class User{
  2. public $name = 'andy';
  3. public $age = 52;
  4. private $phone = '138xxxxxxxx';
  5. private $email = 'xxxx@gmail.com';
  6. protected $location = 'hongkong';
  7. }
  8. var_dump((array) new User());

 

数组的遍历

再来一个简单的问题
数组遍历的方式有哪些?

1.for: 语句循环遍历
2.foreach: 循环遍历
3.while:(list($key, $val) = each($fruit))
4.array_walk、array_map: 回调遍历
5.current和next: 内部指针遍历

array_walk与array_map有什么不同
array_walk引用的方式对数组进行遍历,返回值不重要
array_map为了改变数组的数据,支持多个数组数据合并,目的是返回新的数组。
walk和map的回调函数位置也不一样。

foreach和for性能对比
通过运行示例来体验性能的不同。
一般php推荐使用foreach来遍历数组。

foreach遍历中的顺序
看下面程序的执行结果

  1. $a = array();
  2. $a[2] = 3;
  3. $a[1] = 2;
  4. $a[0] = 1;
  5. foreach ($a as $v) {
  6. echo $v;
  7. }

 

foreach 来访问,是以什么顺序遍历呢?(通过介绍数组的内部结构进一步了解)

下面代码的执行结果是?

  1. $arr = array(1, 2, 3);
  2. foreach($arr as &$v){}
  3. foreach($arr as $v){
  4. echo $v;
  5. }

 

分解:

  1. $arr = array(1, 2, 3);
  2. foreach($arr as &$v){}
  3. echo $v;
  4.  
  5. $arr = array(1, 2, 3);
  6. foreach($arr as &$v){}
  7. echo $v;
  8. foreach($arr as $b) {
  9. echo $v;
  10. }

 

waring: 数组最后一个元素的$value引用在foreach循环之后仍会保留。建议使用 unset()来将其销毁。

foreach遍历中的引用分析

第一次foreach($arr as $k => &$v)
循环1:$v = &$arr[0] = 1;
循环2:$v = &$arr[1] = 2;
循环3:$v = &$arr[2] = 3;
第二次foreach($arr as $k => $v)
循环1:$v = &$arr[2] = arr[0] = 1;此时 $arr[2] = 1;
循环2:$v = &$arr[2] = arr[1] = 2; 此时 $arr[2] = 2
循环3:$v = &$arr[2] = arr[2] = 2;
此时 $arr = array(1, 2, 2);

PHP数组的内部实现

  • 实现数组使用了两个数据结构,一个是HashTable,另一个是bucket.
  • HashTable结构体用于保存整个数组需要的基本信息。
  • Buckey结构体用于保存具体的数据内容

什么是HashTable?
哈希表,是根据关键字(Key value)而直接访问在内存存储位置的数据结构。也就是说,它通过把键值通过一个函数的计算,映射到表中的一个位置来访问记录,这加快了查询速度。这个映射函数称作哈希函数,存放记录的数组称作哈希表。

哈希表
hash table,又叫哈希表,散列表,hash表。
这种数据结构通过key->value的映射关系,使得普通的查找和插入、删除操作都可以在O(1)的时间内完成。
key->value的映射是通过hash函数来实现的。

PHP数组Hashtable结构体

  1. typedef struct_hashtable{
  2. unit nTableSize; //hash buckey 的大小,最小为8,以2x曾铮。
  3. unit nTableMask; //nTableSize-1,索引取值的优化,193491849 & 127
  4. unit nNumOfElements; //hash Buckey 中当前存在的元素个数,count()函数会直接返回此值。
  5. ulong nNextFreeElement; //下一个数字索引的位置
  6. Bucket *plnternalPointer; //当前遍历的指针foreach比for快的原因之一,reset,current遍历函数使用
  7. Bucket *pListHead; //存储数组头元素指针
  8. Bucket *pListTail; //存储数组尾元素指针
  9. Bucket **arBuckets; //存储hash数组,实际的存储容器
  10. Unsigned char nApplyCount; //标记当前hash Bucket被递归访问的次数(防止多次递归)
  11. }HashTable;

Bucket结构体

  1. typedef struct bucket{
  2. ulong h; //对char *key进行hash后的值,或者是用户指定的数字索引值
  3. uint nKeyLength; //hash关键字的长度,如果数组索引为数字,此值为0
  4. void *pData; //指向value, 一般是用户数据的副本,如果是指针数据,则指向pDataPtr
  5. void *pDataPtr; //如果是指针数据,此值会指向真正的value,同事上面pData会指向此值
  6. struct bucket *pListNext; //整个hash表的下一元素
  7. struct bucket *pListLast; //整个hash表该元素的上一个元素
  8. struct bucket *pNext; //存放在同一个hash Bucket内的下一个元素
  9. struct bucekt *pLast; //同一个hash bucket的上一个元素
  10. const char *arKey; //保存当前key所对应的字符串值
  11. }Buckey;

数字索引
直接使用h作为hash值。
arKey = null 且 nKeyLength = 0

字符串索引(关联数组)
h则是该字符串通过hash函数计算后的hash值。
arKey保存字符串key,
nKeyLength 保存该key的长度

typedef struct_zend_hash_key{
const char *arKey;
uint nKeyLength;
ulong h;
}zend_hash_key;

在PHP数组中,如果索引字符串可以被转换成数字,也会被转换成数字索引。
所以在PHP中例如‘10’, ‘11’这类的字符做阴和数字索引10,11没有区别。

HashTable扩容
HashTalbe的大小并不是固定不变的,当nNumOfElements > nTableSize时,会对HashTable进行扩容,以便于容纳更多的元素。
扩容之后需要对原来hashTable的元素rehash.

  1. <?php
  2. $a = [];
  3. $p = (1 << 14) -1; //每一次移动都表示"乘以2"
  4. $b = 1;
  5. for ($i = 0; $i < $p; $i++) {
  6. $a[] = $b;
  7. }
  8. echo memory_get_usage(true)."\n";
  9. $a['as1'] = 1;
  10. echo memory_get_usage(true)."\n";
  11. $a['as2'] = 1;
  12. echo memory_get_usage(true)."\n";

PHP数组排序的原理

由于数组排序并不会改变数组中的元素,而只是改变了数组中元素的位置,因而,对底层而言,实际上只是对全局的双链表进行排序。
1.申请n个额外的空间(n是数组元素个数)
2.然后遍历双链表,将双链表的每个节点存储到临时空间
3.调用排序函数zend_qsort(内部是快速排序算法)对数组进行排序
4.排序之后,双链表中节点的位置发生了变化,因而需要调整指针的指向。
5.遍历数组,分表设置每一个及节点的pListLast和pListNext
6.最后设置HashTable的pListTail

PHP位运算

判断int型变量a是奇数还是偶数:a&1 = 0偶数 a&1 = 1 奇数
乘法运算转化成位运算:a*(2^n)等价于 a<<n
出发运算转化成位运算:a/(2^n)等价于a>>n
不同temp交换两个整数 x^=y;y^=x;x^=y;
二进制位掩码,提供了一种用一个选项表示多项的可能。(参考:error_reporting设置错误级别的方式)

思考:下面两种写法的含义
error_reporting(E_ERROR|E_WARNING|E_PARSE|E_NOTICE);
error_reporting(E_ALL ^ E_NOTICE);

输入流php://input
$_POST vs php://input
①仅在取值为application/x-www-data-urlencoded和multipart/form-data时,PHP会将http请求body响应数据会填入到数组$_POST,填入到$_POST数组中的数据是进行urldecode()解析的结果。
②只要Content-Type不为multipart/form-data,php://input会填入post数据。
③仅当Content-Type为application/x-www-form-urlencoded且提交方法是POST方法时,$_POST数据与php://input数据才是一致的

$HTTP_RAW_POST_DATA VS php://input
php://input可以读取没有处理过的POST数据。相较于$HTTP_RAW_POST_DATA而言,它给内存到来的压力较小。
This feature has been DEFRECATED as of PHP 5.6.0.

数组与数据结构

数组和数据结构之“链表”
链表能存储多个数据元素,他就像一条线一样,把所有存储的数据元素连在一起,所以也称作是线性的数据结构。

数组和数据结构之“散列表”
散列表(Hash table,也叫哈希表),是根据关键字(key value)而直接访问在内存存储位置的一种数据结构。

数组和数据结构之“栈”
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进先出的原则存储数据(LIFO,Last In First Out)。

数组实现“栈”的核心操作
$stack = array(34, 35, 321, 98, 20);
$item = array_pop($stack);//出栈
array_push($stack, $item); //入栈

数组和数据结构之“队列”
队列就是先进先出(FIFO,First-In-First-Out)的线性表。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。

数组实现“队列”的核心操作
$queue = array(2, 3, 4, 5, 6);
array_push($queue, 7); //入队列
$item = array_shift($queue);//出队列

PHP数组解决“约瑟夫环”问题
一群猴子排成一圈,按1,2,...n依次编号。然后从第一只开始数,疏导第m只,把它踢出圈,从它后面再开始数,再数到第m只,再把它踢出去...,如此不停的进行下去,直到只剩下一只猴子为止,那只猴子就叫做大王。
要求编程模拟此过程,输入m、n,输出之后那个大王的编号。

  1. <?php
  2. //php有非常完善的数据结构模拟方案,可以非常简洁的解决这样的问题
  3. function king($n, $m)
  4. {
  5. $monkey = range(1, $n); //模拟建立一个连续数组
  6. $i = 0;
  7. while(count($monkey) > 1) {
  8. $i++;//开始查数
  9. $head = array_shift($monkey);//直接一个一个出列最前面的猴子
  10. if ($i % $m != 0) {
  11. array_push($monkey, $head);//如果么有数到m或m的倍数,则把该候放回尾部去。
  12. } //否则就抛弃掉了
  13. }
  14. return $monkey[0];
  15. }
  16.  
  17. echo '剩余', king(6, 9), '号猴子';

一条评论

  1. 水果一件代发 2019年6月27日 下午6:31 回复

    技术很好

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Go