PHP代码实现2 [从变量和数据的角度] 1

数据类型

1.静态类型语言,比如:C/Java等,在静态语言类型中,类型的检查是在<编译>(compile-time)确定的, 也就是说在运行时变量的类型是不会发生变化的。

2.动态语言类型,比如:PHP,python等各种脚本语言,这类语言中的类型是在[运行时]确定的, 那么也就是说类型通常可以在运行时发生变化

3.无类型语言,比如:汇编语言,汇编语言操作的是底层存储,他们对类型毫无感知。

PHP 8种变量类型

String Int float Boolean Null Resource Object array

在官方的PHP实现内部,所有变量使用同一种数据结构(zval)来保存,而这个结构同时表示PHP中的各种数据类型。 它不仅仅包含变量的值,也包含变量的类型。这就是PHP弱类型的核心。

PHP变量存储结构

1
2
3
4
5
6
7
8
9
typedef struct _zval_struct zval;
...
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */ 变量值
zend_uint refcount__gc; // 引用计数 GC的时候用 默认1
zend_uchar type; /* active type */ 变量类型
zend_uchar is_ref__gc; // 是否为引用 默认0
};

PS:在PHP5.3之后引入了新垃圾收集机制 则refcount -> refcount_gc is_ref -> is_ref_gc

【注意 变量的值存在了zvalue_value这个struct中,那么也就是其的内存占用很玄学,也就是是用union 来巧妙的避开多内存占用】

type:IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT和IS_RESOURCE

PHP变量的值的存储

1
2
3
4
5
6
7
8
9
10
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct { /*string value*/
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;

这里使用联合体而不是用结构体是出于空间利用率的考虑,因为一个变量同时只能属于一种类型。 如果使用结构体的话将会不必要的浪费空间,而PHP中的所有逻辑都围绕变量来进行的,这样的话, 内存浪费将是十分大的。这种做法成本小但收益非常大。

注意到字符串value中加入了int的 len, 这和MyIsam存储结构存储长度的原理是相同的,因为获取到字符串长度的时间复杂度是O(n),字符串在PHP操作中很频繁,为了节约时间开销,直接存储进来。是一种空间换时间的做法

【哈希相关】

通过合理设计的哈希函数,我们就能将key映射到合适的范围,因为我们的key空间可以很大(例如字符串key), 在映射到一个较小的空间中时可能会出现两个不同的key映射被到同一个index上的情况, 这就是我们所说的出现了冲突。 目前解决hash冲突的方法主要有两种:链接法和开放寻址法。

  • 链接法通过使用一个链表来保存slot值的方式来解决冲突,也就是当不同的key映射到一个槽中的时候使用链表来保存这些值。 所以使用链接法是在最坏的情况下,也就是所有的key都映射到同一个槽中了,这样哈希表就退化成了一个链表, 这样的话操作链表的时间复杂度则成了O(n),这样哈希表的性能优势就没有了, 所以选择一个合适的哈希函数是最为关键的。
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _Bucket /** 采用链接法解决哈希冲突的HashTable结构 最坏情况 O(n) **/
{
char *key;
void *value;
struct _Bucket *next;
} Bucket; typedef struct _HashTable
{
int size;
int elem_num;
Bucket** buckets;
} HashTable;
  • 键(key):用于操作数据的标示,例如PHP数组中的索引,或者字符串键等等。
  • 槽(slot/bucket):哈希表中用于保存数据的一个单元,也就是数据真正存放的容器。
  • 哈希函数(hash function):将key映射(map)到数据应该存放的slot所在位置的函数。
  • 哈希冲突(hash collision):哈希函数将两个不同的key映射到同一个索引的情况。

hash_Insert函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int hash_insert(HashTable *ht, char *key, void *value)
{
// check if we need to resize the hashtable
resize_hash_table_if_needed(ht); int index = HASH_INDEX(ht, key); Bucket *org_bucket = ht->buckets[index]; // 获得计算出来的index所在的bucket
Bucket *tmp_bucket = org_bucket; // check if the key-value exits already
while(tmp_bucket)
{
if(strcmp(key, tmp_bucket->key) == 0)
{
LOG_MSG("[update]\tkey: %s\n", key);
tmp_bucket->value = value; return SUCCESS; // 查看当前的key对应的value是否已经存在了,value是不允许重复的
} tmp_bucket = tmp_bucket->next;
} Bucket *bucket = (Bucket *)malloc(sizeof(Bucket)); 分配新的bucket的地址 bucket->key = key;
bucket->value = value;
bucket->next = NULL; 进行相关bucket的赋值 ht->elem_num += 1; HashTable计数增加 if(org_bucket != NULL)
{
LOG_MSG("[collision]\tindex:%d key:%s\n", index, key); 出现冲突
bucket->next = org_bucket; 将新的bucket放在index的第一位 后面进行顺延
} ht->buckets[index]= bucket; LOG_MSG("[insert]\tindex:%d key:%s\tht(num:%d)\n",
index, key, ht->elem_num); return SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static int hash_resize(HashTable *ht)
{
// double the size
int org_size = ht->size;
ht->size = ht->size * 2;
ht->elem_num = 0; //后续重新插入,因为Double size之后 之前的冲突有可能被解开,所以重新Insert LOG_MSG("[resize]\torg size: %i\tnew size: %i\n", org_size, ht->size); Bucket **buckets = (Bucket **)calloc(ht->size, sizeof(Bucket *)); Bucket **org_buckets = ht->buckets; 获取老的buckets
ht->buckets = buckets;// 新的table置为空 int i = 0;
for(i=0; i < org_size; ++i)
{
Bucket *cur = org_buckets[i];
Bucket *tmp;
while(cur)
{
// rehash: insert again
hash_insert(ht, cur->key, cur->value); // free the org bucket, but not the element
tmp = cur;
cur = cur->next;
free(tmp);
}
}
free(org_buckets); LOG_MSG("[resize] done\n"); return SUCCESS;
}

数组是PHP中最常用,也是最强大变量类型,它可以存储其他类型的数据,而且提供各种内置操作函数。数组的存储相对于其他变量要复杂一些, 数组的值存储在zvalue_value.ht字段中,它是一个HashTable类型的数据。 PHP的数组使用哈希表来存储关联数据。哈希表是一种高效的键值对存储结构。PHP的哈希表实现中使用了两个数据结构HashTable和Bucket。 PHP所有的工作都由哈希表实现,在下节HashTable中将进行哈希表基本概念的介绍以及PHP的哈希表实现。(WOC PHP竟然用HashTable存储array,O(1)!)

对象的存储:

1
2
3
4
typedef struct _zend_object_value {
zend_object_handle handle; // unsigned int类型,EG(objects_store).object_buckets的索引
zend_object_handlers *handlers;
} zend_object_value;

PHP的对象只有在运行时才会被创建,前面的章节介绍了EG宏,这是一个全局结构体用于保存在运行时的数据。 其中就包括了用来保存所有被创建的对象的对象池

而object对象值内容的zend_object_handle域就是当前 对象在对象池中所在的索引,handlers字段则是将对象进行操作时的处理函数保存起来。 这个结构体及对象相关的类的结构_zend_class_entry,将在第五章作详细介绍

PHP HashTable实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _hashtable { 
uint nTableSize; // hash Bucket的大小,最小为8,以2x增长。
uint nTableMask; // nTableSize-1 , 索引取值的优化
uint nNumOfElements; // hash Bucket中当前存在的元素个数,count()函数会直接返回此值
ulong nNextFreeElement; // 下一个数字索引的位置
Bucket *pInternalPointer; // 当前遍历的指针(foreach比for快的原因之一)
Bucket *pListHead; // 存储数组头元素指针
Bucket *pListTail; // 存储数组尾元素指针
Bucket **arBuckets; // 存储hash数组
dtor_func_t pDestructor; // 在删除元素时执行的回调函数,用于资源的释放
zend_bool persistent; //指出了Bucket内存分配的方式。如果persisient为TRUE,则使用操作系统本身的内存分配函数为Bucket分配内存,否则使用PHP的内存分配函数。
unsigned char nApplyCount; // 标记当前hash Bucket被递归访问的次数(防止多次递归)
zend_bool bApplyProtection;// 标记当前hash桶允许不允许多次访问,不允许时,最多只能递归3次
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;

PS:mask的作用就是将哈希值映射到槽位所能存储的索引范围内。 例如:某个key的索引值是21, 哈希表的大小为8,则mask为7,则求与时的二进制表示为: 10101 & 111 = 101 也就是十进制的5。 因为2的整数次方-1的二进制比较特殊:后面N位的值都是1,这样比较容易能将值进行映射, 如果是普通数字进行了二进制与之后会影响哈希值的结果。那么哈希函数计算的值的平均分布就可能出现影响。

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct bucket {
ulong h; // 对char *key进行hash后的值,或者是用户指定的数字索引值
uint nKeyLength; // hash关键字的长度,如果数组索引为数字,此值为0
void *pData; // 指向value,一般是用户数据的副本,如果是指针数据,则指向pDataPtr
void *pDataPtr; //如果是指针数据,此值会指向真正的value,同时上面pData会指向此值
struct bucket *pListNext; // 整个hash表的下一元素
struct bucket *pListLast; // 整个哈希表该元素的上一个元素
struct bucket *pNext; // 存放在同一个hash Bucket内的下一个元素
struct bucket *pLast; // 同一个哈希bucket的上一个元素
// 保存当前值所对于的key字符串,这个字段只能定义在最后,实现变长结构体
char arKey[1]; // 是变长结构体 在插入的时候进行单独申请空间就可以
} Bucket;

PS:如上面各字段的注释。h字段保存哈希表key哈希后的值。这里保存的哈希值而不是在哈希表中的索引值, 这是因为索引值和哈希表的容量有直接关系,如果哈希表扩容了,那么这些索引还得重新进行哈希在进行索引映射, 这也是一种优化手段。

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

HashTable的操作接口常见:

  • 初始化操作,例如zend_hash_init()函数,用于初始化哈希表接口,分配空间等。
  • 查找,插入,删除和更新操作接口,这是比较常规的操作。
  • 迭代和循环,这类的接口用于循环对哈希表进行操作。
  • 复制,排序,倒置和销毁等操作。

PS:在PHP中不管是对数组的添加操作(zend_hash_add),还是对数组的更新操作(zend_hash_update), 其最终都是调用_zend_hash_add_or_update函数完成,这在面向对象编程中相当于两个公有方法和一个公共的私有方法的结构, 以实现一定程度上的代码复用。

PHP List链表实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _zend_llist_element {
struct _zend_llist_element *next;
struct _zend_llist_element *prev;
char data[1]; /* Needs to always be last in the struct */
} zend_llist_element; typedef struct _zend_llist {
zend_llist_element *head;
zend_llist_element *tail;
size_t count;
size_t size;
llist_dtor_func_t dtor;
unsigned char persistent;
zend_llist_element *traverse_ptr;
} zend_llist;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ZEND_API void zend_llist_add_element(zend_llist *l, void *element) // List插入操作
{
zend_llist_element *tmp = pemalloc(sizeof(zend_llist_element)+l->size-1, l->persistent); tmp->prev = l->tail;
tmp->next = NULL;
if (l->tail) {
l->tail->next = tmp;
} else {
l->head = tmp;
}
l->tail = tmp;
memcpy(tmp->data, element, l->size); ++l->count;
}

PS:PHP中很多的函数都会有*_ex()以及不带ex两个版本的函数,这主要是为了方便使用, 和上面的代码一样,ex版本的通常是一个功能较全或者可选参数较多的版本, 而在代码中很多地方默认的参数值都一样,为了方便使用,再封装一个普通版本。

PHP代码实现2 [从变量和数据的角度] 1的更多相关文章

  1. PHP代码实现2 [从变量和数据的角度] 2

    常量 常量的数据结构 1234567 typedef struct _zend_constant { zval value; /* zval结构,PHP内部变量的存储结构,在第一小节有说明 */ in ...

  2. 019 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 13 数据类型转换的代码示例

    019 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 13 数据类型转换的代码示例 本文知识点:Java中的数据类型转换案例 学习视频有误,导致没法写文,文章内容 ...

  3. java中静态的代码块,静态变量,静态方法

    简单了解一下java虚拟机--jvm几个内存区域: 方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息.常量.静态变量以及方法代码的内存区域, 常量池:常量池是方法区的一部分,主要用来存放 ...

  4. R语言通过loess去除某个变量对数据的影响

      当我们想研究不同sample的某个变量A之间的差异时,往往会因为其它一些变量B对该变量的固有影响,而影响不同sample变量A的比较,这个时候需要对sample变量A进行标准化之后才能进行比较.标 ...

  5. R语言通过loess去除某个变量对数据的影响--CNV分析

    当我们想研究不同sample的某个变量A之间的差异时,往往会因为其它一些变量B对该变量的固有影响,而影响不同sample变量A的比较,这个时候需要对sample变量A进行标准化之后才能进行比较.标准化 ...

  6. 线程系列07,使用lock语句块或Interlocked类型方法保证自增变量的数据同步

    假设多个线程共享一个静态变量,如果让每个线程都执行相同的方法每次让静态变量自增1,这样的做法线程安全吗?能保证自增变量数据同步吗?本篇体验使用lock语句块和Interlocked类型方法保证自增变量 ...

  7. 在activity之间通过静态变量传递数据

    在activity之间通过静态变量传递数据 一.简介 主要作用:解决intent不能传递非序列化的对象 评价:简单方便,但是容易发生内存泄露,所以要及时回收内存 二.具体操作 1.在传输数据的页面弄好 ...

  8. java静态类、静态方法、静态代码块,静态变量及实例方法,实例变量初始化顺序及内存管理,机制

    1.当一个类被第一次使用时,它需要被类加载器加载,而加载过程涉及以下两点: (1)在加载一个类时,如果它的父类还未被加载,那么其父类必须先被加载: (2)当类加载到内存之后,按照在代码中的出现顺序执行 ...

  9. 【黑马JavaSE】1.1JavaSE、环境变量、CMD使用、常量、变量、数据类型转换(自动/强制)、ASCII码表、Unicode万国码表

    文章目录 SUN公司,詹姆斯.劳瑟琳,Java祖师爷 Java语言开发环境搭建 把Java添加到环境变量的方法 命令行CMD里一些报的错误 命令控制行常用操作的代码展示 Notepad++.注释.标识 ...

随机推荐

  1. 2--Python入门--Python数据集合类型--列表

    在基础数据类型的基础上,Python有6中数据集合的类型: 列表list,最常用的数据类型,以[]为标识 元组tuple,和list很相似,但是不能二次赋值,用()标识 集合set,和list类似,但 ...

  2. POJ 2001 Shortest Prefixes(字典树)

    Description A prefix of a string is a substring starting at the beginning of the given string. The p ...

  3. C++指针易错点梳理

    1 指针定义 指针是一个变量:指针的值是另一个变量的地址.变量的声明 type *var-name; var-name 是指针变量的名称.星号是用来指定一个变量var-name是指针变量. int * ...

  4. Gym - 101889D:Daunting device (老司机树)

    题意:N个格子排出一排,开始格子颜色都是1:现在有M个操作: 或,把区间[L,R]颜色改为c: 或,查询一共有多少格子颜色为c. 最后求颜色最多的数量. 数据是随机的,且强制在线. 思路:ODT裸题. ...

  5. vmware NAT 网络出现问题的解决方法

    nat 网络的配制: 用nat网络相对来说,以后网段出现改变,都不会有大的影响,方便以后做实验. 用dhclient 来获取IP dhclent -r 是用来关闭获取IP的. 自动获取IP,也可以改成 ...

  6. 纯js常用的代码

    1.获取表单中某属性的值 var name = document.myform.myname.value; 2.表单提交时校验,相应js代码中需要返回true或者false <form name ...

  7. Redis(一)入门

    最近,学习了一下,Redis 这个Nosql数据库,从安装到基本语法,作为入门.下面,整理一下基本知识. 参考的地址如下: http://www.runoob.com/redis/redis-java ...

  8. 监控页面后退前进,浏览器文档加载事件之pageshow、pagehide

    https://www.cnblogs.com/milo-wjh/p/6811868.html http://www.runoob.com/jsref/event-onpageshow.html on ...

  9. Hive GenericUDF2

    再来看一个分数统计的小例子. 在Hive中存在如下一张表: hive> describe tb_test2; OK name    string score_list  array<map ...

  10. datetime学习

    四.datetime类 (一).datetime类的数据构成 datetime类其实是可以看做是date类和time类的合体,其大部分的方法和属性都继承于这二个类,相关的操作方法请参阅,本文上面关于二 ...