对 PHP 数组的理解

在 PHP 中数组是使用最多的数据类型.大多数时间,我们不需要考虑 PHP 数组在编码中或者应用中的作用.我们喜欢 PHP 数组的动态特性,有时我们甚至不想去探究是否可以使用除数组意外的数据类型来解决我们的问题.下面我们来探讨一下 PHP 数组的优缺点,同时研究如何用不同的数据类型实现来使用数组实现性能的提升.我们先来解释 PHP 中数组的不同类型.然后来分析一下PHP 数组元素的内存空间以及如何通过其他数据结构来改善性能

更好的理解 PHP 数组

PHP 中的数组实际上是一个有序映射。映射是一种把 values 关联到 keys 的类型。此类型在很多方面做了优化,因此可以把它当成真正的数组,或列表(向量),散列表(是映射的一种实现),字典,集合,栈,队列以及更多可能性。由于数组元素的值也可以是另一个数组,树形结构和多维数组也是允许的。

PHP数组是动态灵活的,我们不需要去关注它是否像其他编程语言一样是一个有规则的,关联的,多维的数组.我们也不需要去定义数组的容量和数据类型.那 PHP 是这么实现这些的呢?答案很简单:PHP 数组的概念不仅仅是一个真正的数组而是一个 Hash-Map.

下面是数组的三种主要的类型:

  • 数字型
  • 关系型
  • 多维数组

数字型数组

数字型数组不仅意味着它能保存数字数据,事实上,它意味着数组额索引只能是数字.

在 PHP 中他们既可以是有序的也可以是无序的,但是他们必须是数字.在数字数组中,值以线性的方式存储和访问.下面举个列子

$array = [10,20,30,40,50];
$array[] = 70;
$array[] = 80;

$arraySize = count($array);
for($i = 0;$i<$arraySize;$i++) {
        echo "Position ".$i." holds the value ".$array[$i]."\n";
}
Position 0 holds the value 10 
Position 1 holds the value 20 
Position 2 holds the value 30 
Position 3 holds the value 40 
Position 4 holds the value 50 
Position 5 holds the value 70 
Position 6 holds the value 80  

上面是一个简单的数字型数组的实现,我们可以看到当我们添加通过 $array[] 在数组中添加一个新的元素,数字自动增加索引并且在新的索引上赋值.

如果我的数组是顺序的,我们可以使用 for 进行遍历.

一个大的问题的是,如果索引不是顺序的,难道我们不能描述数组了吗? 当然是可以的,看下面的例子

$array = [];
$array[10] = 100;
$array[21] = 200;
$array[29] = 300;
$array[500] = 1000;
$array[1001] = 10000;
$array[71] = 1971;

foreach($array as $index => $value) {
    echo "Position ".$index." holds the value ".$value."
"; }
Position 10 holds the value 100 
Position 21 holds the value 200 
Position 29 holds the value 300 
Position 500 holds the value 1000 
Position 1001 holds the value 10000 
Position 71 holds the value 1971

关系型数组

关系型数组通过键(可以是任意字符串)来存取数据.在关系型数组中,通过键取代线性索引来存储值.和数字数组一样,我们可以通过关系型数组来存储任意类型的数据.

下面来看一组学生信息的例子:

$studentInfo = [];
$studentInfo['Name'] = "Adiyan";
$studentInfo['Age'] = 11;
$studentInfo['Class'] = 6;
$studentInfo['RollNumber'] = 71;
$studentInfo['Contact'] = "[email protected]";

foreach($studentInfo as $key => $value) {
    echo $key.": ".$value."\n";
 }

输出

Name: Adiyan 
Age: 11 
Class: 6 
RollNumber: 71 
Contact: [email protected] 

这里我们使用每一个键来保存数据.我们可以根据我们的需求添加键.我们利用 PHP 数组灵活的展示类似于结构体,map, 字典这些数据结构.

多维数组

顾名思义,多维数组就是存储了多个数组.换一句话说,它是存储数组的数组.这里将会在不同的例子中使用多维数组

使用多维数组来表示数据结构

在接下来的内容中,我们将会讨论不同的数据结构和算法.一个键的数据结构:图(graph).大多数时候我们都将使用 PHP 多维数组来表示邻接矩阵

接下来分析一下下面的这个图
[图片上传失败...(image-e97294-1511863276604)]

现在如果我们认为图的每一个节点是数组的一个值,我们可以这样描述节点

$node = ['A','B','C','D','E'];

但是上面的表达式无法表示节点间的连接关系,因此我们需要设计一个二维数组,这个二维数组 keys 表示names, 基于两个节点的内联 values 为0或者1.因为提供的图没设定方向所以我们假设这里是双向连接.

首先,我们需要为图创建一个数组,并且初始化二维数组的每个节点的值是0.代码如下:

$graph = [];
$nodes = ['A', 'B', 'C', 'D', 'E'];
foreach ($nodes as $xNode) {
    foreach ($nodes as $yNode) {
        $graph[$xNode][$yNode] = 0;
    }
}

打印 *$graph

0       0       0       0       0
0       0       0       0       0
0       0       0       0       0
0       0       0       0       0
0       0       0       0       0

现在我们将这样定义节点间的连接,每两个节点相连将会被表示为1:

$graph["A"]["B"] = 1;
$graph["B"]["A"] = 1;
$graph["A"]["C"] = 1;
$graph["C"]["A"] = 1;
$graph["A"]["E"] = 1;
$graph["E"]["A"] = 1;
$graph["B"]["E"] = 1;
$graph["E"]["B"] = 1;
$graph["B"]["D"] = 1;
$graph["D"]["B"] = 1; 

因为图谱中是无方向的,我们将认为是无方向的树,因此我们对于每一个连接都设置为1.后面我们会了解到为什么我这做.现在我们继续打印 *$graph:

0       1       1       0       1
1       0       0       1       1
1       0       0       0       0
0       1       0       0       0
1       1       0       0       0

好了,在后续关于图的实际操作的文章中我们将继续探讨这个数据结构.

利用 SplFixedArray 生成固定长度的数组

聊了这么多,我们已经对 PHP 数组有了很深入的了解了,但是我们从没有定义过数组的长度.PHP 数组长度可以根据我们的命令增长或者缩小.这一灵活性来自于强大的内存管理.我们下面就来探讨这些内容,来看看使用 PHP SPL 来生成固定长度的 PHP 数组.

为什么我们不生成固定长度的 PHP 数组呢?难道没有任何好处吗?答案是:当我们仅仅需要数组中某一个数字的时候,我们使用固定长度的数组可以减少内存的使用.在进行内存分析的之前,让我们使用 SplFixedArray 举个例子:

$array = new \SplFixedArray(10);

for ($i = 0; $i < 10; $I++)
    $array[$i] = $I;

for ($i = 0; $i < 10; $I++)
    echo $array[$i] . "\n";

输出结果为0-9,当我访问超过10的索引将会报错.

PHP Fatal error:  Uncaught RuntimeException: Index invalid or out of range 

PHP数组和SplFixedArray的基本区别为:

  • SplFixedArray必须有有限固定的长度
  • SplFixedArray的索引必须为integers且在0-n 范围之内, n 为我们定义的数组长度

当我们有很多有限固定长度数组或者有数组长度限制的时候 SplFixedArray 将会很好用.

常规 PHP 数组和 SplFixedArray 性能比较

在上一节中我们认为的一个关键的问题:为什么我们要要使用 SplFixedArray 取代传统 PHP 数组?通过对 PHP 数组的概念的理解,PHP 数组不仅仅是数组,还是 hash maps. 下面来运行一个例子来看一下 PHP 5.x 中 PHP 数组内存使用

创建一个100000唯一的整数数组. 每一个整数占用8 bytes,所以会消耗800000 bytes

$startMemory = memory_get_usage();
$array = range(1,100000);
$endMemory = memory_get_usage();
echo ($endMemory - $startMemory)." bytes";

输出

14649032 bytes

我们可以看见结果是 14649032 bytes,几乎是设想的18.5倍.意味着数组中的每一个元素超过144 bytes (18 * 8 bytes). 那么,额外的144 bytes来自哪里?为什么 PHP 没有利用这额外的内存? 这里有一个关于PHP数组额外内存占用的解释:

[图片上传失败...(image-116276-1511863276604)]

这幅图展示了 PHP 数组内部是如何工作的.PHP 数组存储将数据存储在bucket中.为了管理这个动态特性,这里可以看到这里为数组实现了双向链表和 hash 表.结果就导致耗费大量额外的内存.

在 PHP 7中 PHP 数组得到了优化:

$array = Range(1,10000) 32 bit 64 bit
PHP 5.6 or below 7.4MB 14MB
PHP 7 3 MB 4 MB

在 PHP7中对64位系统数组优化了3.5倍,下面来看看 SplFixedArray:

$items = 100000;
$startMemory = memory_get_usage();
$array = new \SplFixedArray($items);
for ($i = 0; $i < $items; $i++) {
    $array[$i] = $I;
}
$endMemory = memory_get_usage();

$memoryConsumed = ($endMemory - $startMemory) / (1024*1024);
$memoryConsumed = ceil($memoryConsumed);
echo "memory = {$memoryConsumed} MB\n";
memory = 2 MB
100000 条数据 使用 PHP 数组 64 bit
PHP 5.6 or below 14MB 6 MB
PHP 7 5 MB 2 MB

不仅在内存使用率上,SplFixedArray而且在进行数组比较,取值,赋值上都更快.

更多使用 SplFixedArray 的例子

既然 SplFixedArray 在性能方面有更好的表现,我们尽量在数据类型和算法中使用它.

将 PHP 数组转化为 SplFixedArray

我们已经知道如何创建一个定长的SplFixedArray,那么怎么实时的将一个普通 PHP 数组转化为SplFixedArray?

$array =[1 => 10, 2 => 100, 3 => 1000, 4 => 10000]; 
$splArray = SplFixedArray::fromArray($array); 
print_r($splArray);
SplFixedArray Object
(
    [0] => 
    [1] => 10
    [2] => 100
    [3] => 1000
    [4] => 10000
)

当我们想要实时的将一个数组转化为一个定长的数组,如果我们后面不再使用常规数组,最好 unset 常规数组,这样会最大限度的节省内存

将 SplFixedArray 转化为 PHP 数组

我需要将 SplFixedArray 转化为 PHP 数组来使用很多系统预定义的 array 函数.

$items = 5; 
$array = new \SplFixedArray($items); 
for ($i = 0; $i < $items; $i++) { 
    $array[$i] = $i * 10; 
} 

$newArray = $array->toArray(); 
print_r($newArray);
Array 
( 
    [0] => 0 
    [1] => 10 
    [2] => 20 
    [3] => 30 
    [4] => 40 
)

SplFixedArray 定义后改变长度

$items = 5; 
$array = new \SplFixedArray($items); 
for ($i = 0; $i < $items; $i++) { 
    $array[$i] = $i * 10; 
} 

$array->setSize(10); 
$array[7] = 100; 

使用 SplFixedArray 生成多维数组

$array = new \SplFixedArray(100);

for ($i = 0; $i < 100; $i++) 
    $array[$i] = new \SplFixedArray(100);

你可能感兴趣的:(对 PHP 数组的理解)