Geo*_*ili 22 php arrays memory-management php-internals
我总是听到并搜索新的PHP"良好的写作练习",例如:检查数组键是否存在比搜索数组更好(性能),但对内存来说似乎也更好:
假设我们有:
$array = array
(
'one' => 1,
'two' => 2,
'three' => 3,
'four' => 4,
);
Run Code Online (Sandbox Code Playgroud)
这会分配1040个字节的内存,
和
$array = array
(
1 => 'one',
2 => 'two',
3 => 'three',
4 => 'four',
);
Run Code Online (Sandbox Code Playgroud)
需要1136个字节
据我所知,key
并value
肯定都会有不同的存储机制,但请你其实可以点我的原则,它是如何工作的?
示例2 (对于@teuneboon):
$array = array
(
'one' => '1',
'two' => '2',
'three' => '3',
'four' => '4',
);
Run Code Online (Sandbox Code Playgroud)
1168个字节
$array = array
(
'1' => 'one',
'2' => 'two',
'3' => 'three',
'4' => 'four',
);
Run Code Online (Sandbox Code Playgroud)
1136个字节
消耗相同的内存:
4 => 'four',
'4' => 'four',
Alm*_* Do 25
注意,下面的答案适用于版本7 之前的PHP ,因为在PHP 7中引入了主要的更改,这些更改也涉及值结构.
你的问题实际上并不是关于"PHP中的内存是如何工作的"(这里,我认为,你的意思是"内存分配"),而是关于"数组如何在PHP中工作" - 而这两个问题是不同的.总结一下下面的内容:
ulong
(无符号长)键散列映射中,因此真正的差异将在值中,其中string-keys选项具有整数(固定 - length)值,而integer-keys选项具有字符串(chars-dependent length)值.但由于可能的碰撞,这可能并非总是如此.'4'
,将被视为整数键并转换为整数哈希结果,因为它是整数键.因此,'4'=>'foo'
和4 => 'foo'
相同的事情.另外,重要提示:此处的图形是PHP内部书籍的版权
PHP数组和C数组
你应该意识到一个非常重要的事情:PHP是用C语言编写的,其中诸如"关联数组"之类的东西根本不存在.因此,在C"数组"中正好是"数组" - 即它只是存储器中的连续区域,可以通过连续的偏移来访问.您的"键"可能只是数字,整数,只能是连续的,从零开始.你不能拥有,例如3
,-6
,'foo'
为你的"钥匙"在那里.
因此,为了实现PHP中的数组,有散列映射选项,它使用散列函数来散列键并将它们转换为整数,这些整数可用于C数组.但是,该函数永远无法在字符串键及其整数散列结果之间创建双射.这很容易理解为什么:因为基数设置字符串的多,更大的是整数集的基数.让我们来举例说明与示例:我们将讲述所有的字符串,最大长度是10,其中只有字母数字符号(所以0-9
,a-z
并且A-Z
,共有62个):这是62个10总字符串可能.它大约是8.39E + 17.将它与我们对无符号整数(长整数,32位)类型的4E + 9进行比较,你会得到这个想法 - 会有碰撞.
PHP哈希映射键和冲突
现在,为了解决冲突,PHP只会将具有相同哈希函数结果的项放入一个链表中.因此,hash-map不仅仅是"散列元素列表",而是存储指向元素列表的指针(某些列表中的每个元素都具有相同的散列函数键).这就是你要指出它将如何影响内存分配的地方:如果你的数组有字符串键,这不会导致冲突,那么这些列表中就不需要额外的指针,因此内存量会减少(实际上,它是一个非常小的开销,但是,因为我们正在谈论精确的内存分配,所以应该考虑到这一点).并且,同样地,如果您的字符串键将导致许多冲突,那么将创建更多的附加指针,因此总内存量将更多一些.
为了说明这些列表中的关系,这里是一个图形:
上面是PHP在应用哈希函数后如何解决冲突.因此,您的一个问题部分就在于此处,指出了碰撞解决方案列表中的指针.此外,链表的元素通常称为存储桶,并且包含指向这些列表的头部的指针的数组在内部调用arBuckets
.由于结构优化(因此,为了使元素删除,更快),真正的列表元素有两个指针,前一个元素和下一个元素 - 但这只会使非冲突/碰撞数组的内存量有所不同,但不会改变概念本身.
还有一个清单:订单
要完全支持PHP中的数组,还需要维护顺序,以便通过另一个内部列表实现.数组的每个元素也是该列表的成员.它在内存分配方面没有区别,因为在这两个选项中都应该维护这个列表,但是为了全面了解,我提到了这个列表.这是图形:
除了pListLast
和之外pListNext
,还存储了指向订单列表头部和尾部的指针.同样,它与你的问题没有直接关系,但是我会转储内部存储桶结构,这些存在这些指针.
内部的数组元素
现在我们准备调查:什么是数组元素,所以,桶:
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;
Run Code Online (Sandbox Code Playgroud)
我们到了:
h
是key的整数(ulong)值,它是hash函数的结果.对于整数键,它与键本身相同(哈希函数返回自身)pNext
/ pLast
是冲突解决链接列表中的指针pListNext
/ pListLast
是订单分辨率链表中的指针pData
是指向存储值的指针.实际上,值与在数组创建时插入的值不同,它是副本,但为了避免不必要的开销,PHP使用pDataPtr
(so pData = &pDataPtr
)从这个角度来看,你可能会得到下一个不同之处:因为字符串键将被散列(因此,h
总是ulong
,因此,大小相同),这将是存储在值中的问题.因此,对于您的字符串键数组,将存在整数值,而对于整数键数组,将存在字符串值,这会产生差异.但是 - 不,这不是一个魔术:你不能一直"存储"存储字符串键,因为如果你的键很大并且会有很多键,它会导致冲突开销(好吧,很有可能,但当然不能保证).它将仅对任意短字符串"起作用",这不会导致许多冲突.
哈希表本身
已经讨论过元素(桶)及其结构,但也有散列表本身,实际上是数组数据结构.所以,它被称为_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;
Run Code Online (Sandbox Code Playgroud)
我不会描述所有字段,因为我已经提供了很多信息,这只与问题有关,但我将简要描述这个结构:
arBuckets
就是上面所描述的,水桶存储,pListHead
/ pListTail
是指向订单分辨率列表的指针nTableSize
确定哈希表的大小.这与内存分配直接相关:nTableSize
总是2的幂.因此,无论你在数组中是否有13个或14个元素:实际大小为16.当你想要估计数组大小时,请考虑到这一点.这很难预测,在你的情况下,一个阵列会比另一个阵列大.是的,有一些指导方针跟随内部结构,但如果字符串键的长度与整数值(例如'four'
,'one'
在您的样本中)相当- 真正的区别在于 - 发生了多少次碰撞,多少字节是分配以保存值.
但是选择合适的结构应该是意义上的问题,而不是记忆.如果您打算构建相应的索引数据,那么选择总是显而易见的.上面的帖子只有一个目标:展示数组如何在PHP中实际工作,以及在哪里可以找到样本中内存分配的差异.
您还可以在PHP中检查有关数组和散列表的文章:它是PHP内部的PHP中的Hash表:我从那里使用了一些图形.另外,要了解如何在PHP中分配值,请查看zval Structure文章,它可以帮助您理解,对于数组值的字符串和整数分配会有什么区别.我没有在这里包含解释,因为对我来说更重要的一点是 - 显示数组数据结构以及在您的问题的字符串键/整数键的上下文中可能有什么不同.