我一直都在听和搜索新的php“好的写作习惯”,例如:检查数组键是否比在数组中搜索更好(以提高性能),但对于内存似乎也更好:
假设我们有:
$array = array
(
'one' => 1,
'two' => 2,
'three' => 3,
'four' => 4,
);
这会分配1040字节的内存,
和
$array = array
(
1 => 'one',
2 => 'two',
3 => 'three',
4 => 'four',
);
需要1136字节
我知道
key
和value
肯定会有不同的存储机制,但是请您能实际给我指出该原理是如何工作的吗?
示例2(用于@teuneboon):
$array = array
(
'one' => '1',
'two' => '2',
'three' => '3',
'four' => '4',
);
1168字节
$array = array
(
'1' => 'one',
'2' => 'two',
'3' => 'three',
'4' => 'four',
);
1136字节
消耗相同的内存:
4 => 'four',
'4' => 'four',
最佳答案
注意,下面的答案适用于版本7之前的php ,因为PHP 7中引入了主要更改,其中还涉及值结构。
TL; DR
您的问题实际上不是关于“内存如何在PHP中工作”(在这里,我假设您的意思是“内存分配”),而是关于“数组如何在PHP中工作”-这两个问题是不同的。总结以下内容:ulong
(无符号长)键哈希映射,因此真正的区别在于值,其中string-keys选项具有整数(固定长度)值,而整数键选项具有字符串(取决于字符的长度)值。但是由于可能发生的冲突,这种情况可能并不总是正确的。 '4'
)将被视为整数键,并因为它是整数键而转换为整数哈希结果。因此,'4'=>'foo'
和4 => 'foo'
是相同的东西。
另外,重要的注意事项:此处的图形是PHP internals book的版权
适用于PHP数组的哈希图
PHP数组和C数组
您应该意识到一件非常重要的事情:PHP是用C编写的,根本没有诸如“关联数组”之类的东西。因此,在C中,“数组”正是“数组”的含义-即,它只是内存中的连续区域,可以通过连续的偏移量对其进行访问。您的“键”只能是数字,整数,并且只能是连续的(从零开始)。例如,您不能在那里使用3
,-6
,'foo'
作为您的“键”。
因此,要实现PHP中的数组,有一个hash-map选项,它使用哈希函数对您的键进行哈希处理并将它们转换为整数,该整数可用于C数组。但是,该函数将永远无法在字符串键及其整数散列结果之间创建bijection。而且很容易理解为什么:因为字符串集的cardinality比整数集的基数大得多。让我们用示例进行说明:我们将重新计算所有长度不超过10的字符串,这些字符串仅包含字母数字符号(因此,0-9
,a-z
和A-Z
,总共62个):总共可能有6210个字符串。大约是 8.39E + 17 。将它与 4E + 9 进行比较,我们对无符号整数(长整数,32位)类型有这种理解,您就会明白-将会发生冲突。
PHP哈希映射键和冲突
现在,要解决冲突,PHP会将具有相同哈希函数结果的项目放在一个链接列表中。因此,哈希映射将不只是“哈希元素列表”,而是存储指向元素列表的指针(某些列表中的每个元素都具有相同的哈希函数键)。这就是您将如何影响内存分配的地方:如果您的数组具有不会导致冲突的字符串键,则这些列表内不需要其他指针,因此将减少内存量(实际上,它是开销很小,但是,由于我们在谈论精确的内存分配,因此应该考虑到这一点)。而且,以同样的方式,如果您的字符串键将导致许多冲突,则将创建更多的额外指针,因此总存储量将更多。
为了说明这些列表中的关系,下面是一个图形:
上面是PHP在应用哈希函数后如何解决冲突的方法。因此,您要解决的问题之一就是冲突解决列表中的指针。此外,链接列表的元素通常称为存储桶,而包含指向这些列表的开头的指针的数组在内部称为arBuckets
。由于结构优化(因此,为了使元素删除等操作更快),真实列表元素具有两个指针,前一个元素和下一个元素-但这只会使非冲突/冲突数组的内存量差异变大,但不会改变概念本身。
还有一个列表:订购
要完全支持PHP中的数组,还需要维护顺序,因此可以使用另一个内部列表来实现。数组的每个元素也是该列表的成员。就内存分配而言,这不会有任何区别,因为应该在两个选项中都保留此列表,但是为了全面介绍,我要提到此列表。这是图形:
除了pListLast
和pListNext
之外,还存储了指向订单列表开头和结尾的指针。同样,它与您的问题没有直接关系,但是我将进一步转储内部存储桶结构,其中存在这些指针。
内部的数组元素
现在我们准备开始研究:什么是数组元素,所以bucket:
typedef struct bucket {
ulong h;
uint nKeyLength;
void *pData;
void *pDataPtr;
struct bucket *pListNext;
struct bucket *pListLast;
struct bucket *pNext;
struct bucket *pLast;
char *arKey;
} Bucket;
我们来了:
h
是key的整数(ulong)值,它是哈希函数的结果。对于整数键,它的格式为,与键本身的格式相同(哈希函数返回自身)pNext
/pLast
是冲突解决链表pListNext
/pListLast
是顺序分辨率链表pData
是指向存储值的指针。实际上,值与创建数组时插入的值不同,它是副本,但是为了避免不必要的开销,PHP使用了pDataPtr
(so pData = &pDataPtr
)从这个角度出发,您可能会发现下一个区别:字符串键将被散列(因此,
h
始终为ulong
,因此大小相同),这将取决于存储在值中的内容。因此,对于您的string-keys数组,将有整数值,而对于integer-keys数组,将有字符串值,这有所作为。但是-不,这不是神奇的:您不能一直以这种方式存储字符串键来“保存内存”,因为如果您的键很大并且会有很多键,它将导致碰撞开销(嗯,很有可能,但是当然不能保证)。它仅对任意短字符串“起作用”,不会引起很多冲突。哈希表本身
已经讨论过元素(存储桶)及其结构,但是哈希表本身也存在,实际上是数组数据结构。因此,它称为
_hashtable
:typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements;
ulong nNextFreeElement;
Bucket *pInternalPointer; /* Used for element traversal */
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;
我不会描述所有字段,因为我已经提供了很多信息,而这仅与问题有关,但是我将简要描述此结构:
arBuckets
是上面描述的内容,存储桶是pListHead
/pListTail
是订单解决方案列表的指针nTableSize
确定哈希表的大小。这与内存分配直接相关:nTableSize
始终为2的幂。因此,数组中有13或14个元素都没有关系:实际大小将为16。要估计数组大小时,请考虑到这一点。 结论
很难预测,在您的情况下,一个数组是否会比另一个数组大。是的,有一些遵循内部结构的准则,但是如果字符串键的长度与整数值(例如,示例中的
'four'
,'one'
)相当,那么实际的区别就在于-发生了多少次碰撞,发生了多少次碰撞分配了字节来保存值。但是选择合适的结构应该是有意义的,而不是内存。如果您打算建立相应的索引数据,那么选择总是显而易见的。上面的文章仅是一个目标:展示数组在PHP中的实际工作方式,以及在示例中您可以找到内存分配差异的地方。
您还可以查看有关PHP中的数组和哈希表的文章:PHP内部人员手册中的Hash-tables in PHP:我从那里开始使用了一些图形。另外,要了解如何在PHP中分配值,请查看zval Structure文章,它可能会帮助您了解数组值的字符串和整数分配之间的区别。我在这里没有包括它的解释,因为对我来说更重要的一点是-显示数组数据结构以及在您的问题的字符串键/整数键的上下文中可能有什么不同。
关于php - PHP内存实际如何工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25081344/