五十一、PHP内核探索:数组与链表 ☞ C语言中数组和链表的一些事情_定义namelist c语言_行善积德韩老魔的博客-程序员秘密

技术标签: php底层探究  

在C语言中,我们可以自定义各种各样的数据结构,用来把很多数据保存在一个变量里面,但是每种数据结构都有自己的优缺点,PHP内核规模如此庞大,是否已经找到了一些非常棒的解决方法呢?

我们在选择各种数据结构时,往往会考虑我们需要处理的数据规模以及需要的性能。下面让我们简要的看一下看C语言中数组和链表的一些事情。

数组

作者这里用的不是Array,而是Vector,可能指的是C++里的Vector,它与数组几乎是完全一样的,唯一的不同便是可以实现动态存储。本节下文都是用数组一词代替之,请各位注意。数组是内存中一块连续的区域,其每一个元素都具有一个唯一的下标值。

int a[3];
a[0]=1;
a[2]=3;

不仅是整数,其它类型的变量也可以保存在数组中,比如我们前面用到的zend_get_parameters_array_ex(),便把很多zval**类型的变量保存到一个数组里,为了使其正常工作,我们提前向系统申请了相应大小的内存空间。

zval ***args = safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval**), 0);

这里我们仍然可以用一个整数来当作下标去数组中取出我们想要的数据,就像var_dump()的实现中通过args[i]来获取参数并把它传递给php_var_dump()函数那样。

使用数组最大的好处便是速度!读写都可以在O(1)内完成,因为它每个元素的大小都是一致的,只要知道下标,便可以瞬间计算出其对应的元素在内存中的位置,从而直接取出或者写入。

链表

链表也是一种经常被使用的一种数据结构。链表中的每一个元素都至少有两个元素,一个指向它的下一个元素,一个用来存放它自己的数据,就像下面定义的那样:

typedef struct _namelist namelist;
struct
{
	struct _namelist *next;
	char *name;
}_namelist;

我们可以声明一个其类型的元素:

static namelist *people;

假设每一个元素都代表一个人,元素中的name属性便是这个人的名字,我们通过这样的语句来得到它:people->name; 第二个属性指向后面的一个元素,那我们便可以这样来访问下一个人的名字:people->next->name, 或者下一个人的下一个人的名字:people->next->next->name,一次类推,直到next的值是NULL,代表结束。

//通过一个循环来遍历这个链表中的所有人~
void name_show(namelist *p)
{
	while (p)
	{
		printf("Name: %s\n", p->name);
		p = p->next;
	}
}

链表可以被用来实现FIFO模式,达到先进者先出的目的!

static namelist *people = NULL, *last_person = NULL;
void name_add(namelist *person)
{
    person->next = NULL;
    if (!last_person) {
        /* No one in the list yet */
        people = last_person = person;
        return;
    }
    /* Append new person to the end of the list */
    last_person->next = person;

    /* Update the list tail */
    last_person = person;
}
namelist *name_pop(void)
{
    namelist *first_person = people;
    if (people) {
    	people = people->next;
    }
    return first_person;
}

这样,我们便可以随意的向这个链表中添加或者删除数据,而不向数组那样,谨慎的考虑是否越界等问题。

上面实现的结构的学名叫做单向链表,也有地方叫单链表,反正是比较简单的意思~。它有一个致命的缺点,就是我们在插入或者读取某条数据的时候,都需要从这个链表的开始,一个个元素的向下寻找,直到找到这个元素为止。如果链表中的元素比较多,那它很容易成为我们程序中的CPU消耗大户,进而引起性能问题。为了解决这个问题,先人们发明了双向链表:

typedef struct _namelist namelist;
struct
{
	namelist *next, *prev;
	char *name;
} _namelist;

改动其实不大,就是在每个元素中都添加了一个prev属性,用来指向它的上一个元素。

void name_add(namelist *person)
{
	person->next = NULL;
	if (!last_person)
	{
		/* No one in the list yet */
		people = last_person = person;
		person->prev = NULL;
		return;
	}
	/* Append new person to the end of the list */
	last_person ->next = person;
	person->prev = last_person;

	/* Update the list tail */
	last_person = person;
}

单单通过上面的程序你还体会不到它的好处,但是设想一下,如果现在你有这个链表中其中一个元素的地址,并且想把它从链表中删除,那我们该怎么做呢?如果是单向链表的话,我们只能这样做:

void name_remove(namelist *person)
{
    namelist *p;
    if (person == people) {
        /* Happens to be the first person in the list */
        people = person->next;
        if (last_person == person) {
            /* Also happens to be the last person */
            last_person = NULL;
        }
        return;
    }
    /* Search for prior person */
    p = people;
    while (p) {
        if (p->next == person) {
            /* unlink */
            p->next = person->next;
            if (last_person == person) {
                /* This was the last element */
                last_person = p;
            }
            return;
        }
        p = p->next;
    }
    /* Not found in list */
}

现在让我们来看看双向链表是怎样来处理这个问题的:

void name_remove(namelist *person)
{
    if (people == person) {
        people = person->next;
    }
    if (last_person == person) {
        last_person = person->prev;
    }
    if (person->prev) {

        person->prev->next = person->next;
    }
    if (person->next) {
        person->next->prev = person->prev;
    }
}

对元素的遍历查找不见了,取而代之的是一个O(1)的运算,这将极大的提升我们程序的性能。

王者归来:HashTable才是我们的银弹!

也许你已经非常喜欢使用数组或者链表了,但我还是要向你推荐一种威力极大的数据结构,有了它之后,你可能会立即抛弃前两者,它就是HashTable.

HashTable即具有双向链表的优点,同时具有能与数据匹敌的操作性能,这个数据结构几乎是PHP内核实现的基础,我们在内核代码的任何地方都发现它的痕迹。

前面我们接触过,所有的用户端定义的变量保存在一个符号表里,而这个符号表其实就是一个HashTable,它的每一个元素都是一个zval*类型的变量。不仅如此,保存用户定义的函数、类、资源等的容器都是以HashTable的形式在内核中实现的。

Zend Engine中HashTable的元素其实是指针,对其的这个改进使得HashTable能够包容各种类型的数据,从小小的标量,到复杂的PHP5中实现的类等复合数据。本章接下来的内容,我们将详细的研究如何使用zend内置的API来操作HashTable这个数据结构。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_35458793/article/details/100512381

智能推荐

第五篇:SpringBoot集成swagger2_药岩的博客-程序员秘密

什么是swagger?Swagger2 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。swagger2有何作用?接口的文档在线自动生成。功能测试。swagger2是一组开源项目Swagger-tools:提供各种与Swagger进行集成和交互的工具。例如模式检验、Swagger 1.2文档转换成Swagger 2.0文档等功能。...

WPF的依赖属性_小同不一的博客-程序员秘密

一、依赖属性的全面解析  听到依赖属性,自然联想到C#中属性的概念。C#中属性是抽象模型的核心部分,而依赖属性是专门基于WPF创建的。在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性,但我们必须明确,在WPF中我们大多数都在使用依赖属性,而不是使用属性。依赖属性重要性在于,在WPF核心特性,如动画、数据绑定以及样式中都需要使用到依赖属性

syspolicy_purge_history执行失败的问题(满足你的强迫症)_简码笔记的博客-程序员秘密

最新新搭了一个SQLServer群集实例,在查看SQLAgent执行计划的时候,总是有一个系统任务执行失败。那么这个任务是干嘛的呢?会不会影响数据库正常使用呢?官方解释是:This procedure runs periodically via SQL Agent (msdb.dbo.sp_syspolicy_create_purge_job creates a SQL Agent job to run it), and it cleans out the execution history for

java学习(转载)_maxtomb的博客-程序员秘密

HelloJava1、Java开发工具JDK的安装 2、 JDK的命令工具 JDK的最重要命令行工具: java: 启动JVM执行class javac: Java编译器 jar: Java打包工具 javadoc: Java文档生成器 这些命令行必须要非常非常熟悉,对于每个参数都要很精通才行。对于这些命令的学习,JDK

单例模式实现_爱吃血肠的博客-程序员秘密

以下都是线程安全的,线程不安全的不搞了 饿汉式: package com.neusoft.single;import java.util.concurrent.*;/** * @author Administrator * 饿汉式 * 在加载的时候已经被实例化,所以只有这一次,线程安全的。JVM ClassLoader...

随便推点

nginx下TCP负载均衡配置_nginx tcp 负载均衡_清梦旅人的博客-程序员秘密

负载均衡基础与nginx下TCP负载均衡配置安装nginx在服务器快速集群环境搭建中,都迫切需要一个能拿来即用的负载均衡器,nginx在1.9版本之前,只支持http协议web服务器的负载均衡,从1.9版本开始以后,nginx开始支持tcp的长连接负载均衡,但是nginx默认并没有编译tcp负载均衡模块,编写它时,需要加入--with-stream参数来激活这个模块。nginx编译安装需要先安装pcre、openssl、zlib等库,也可以直接编译执行下面的configure命令,根据错误提示信息

DataStructures - 04 :二叉树_二叉树与数组_线索化_堆_我一直在流浪的博客-程序员秘密

文章目录1、二叉树1.1 二叉树的遍历1.2 二叉树查找某个节点1.3 二叉树删除某个节点2、顺序存储二叉树3、线索化二叉树3.1 实现线索化二叉树3.2 线索化二叉树的遍历4、二叉堆4.1 堆的概念4.2 堆排序1、二叉树1.1 二叉树的遍历1、概念:前序遍历:根节点、当前节点左子树、当前节点右子树: 1、2、3、5、4中序遍历:当前节点左子树、 根节点、当前节点右子树:2、...

R7-3 福到了 (10 分)_r7-1福到了_真题OK撒的博客-程序员秘密

R7-3 福到了 (10 分)“福”字倒着贴,寓意“福到”。不论到底算不算民俗,本题且请你编写程序,把各种汉字倒过来输出。这里要处理的每个汉字是由一个 N×N 的网格组成的,网格中的元素或者为字符@或者为空格。而倒过来的汉字所用的字符由裁判指定。输入格式:输入在第一行中给出倒过来的汉字所用的字符、以及网格的规模 N (不超过100的正整数),其间以 1 个空格分隔;随后 N 行,每行给出 N 个字符,或者为@或者为空格。输出格式:输出倒置的网格,如样例所示。但是,如果这个字正...

Java社招面经分享!启动mysql数据库_普通网友的博客-程序员秘密

四面阿里面试岗位是研发工程师,直接找蚂蚁金服的大佬进行内推,参与了阿里巴巴中间件部门的提前批面试,一共经历了四次面试,拿到了口头offer。一面:自我介绍项目中做了什么,难点呢。Java的线程池说一下,各个参数的作用,如何进行的。Redis讲一下分布式系统的全局id如何实现。用zookeeper如何实现的呢,机器号+时间戳即可。分布式锁的方案,redis和zookeeper那个好,如果是集群部署,高并发情况下哪个性能更好。kafka了解么,了解哪些消息队列。想做业务还是研究。然后出了

时间和日期格式_eee, d mmm_ZiMinqun的博客-程序员秘密

字母 日期或时间元素 表示 示例G Era 标志符 Text AD y 年 Year 1996; 96 M 年中的月份 Month July; Jul; 07 w 年中的周数 Number 27 W 月份中的周数 Number 2 D 年中的天数 Number 189 d 月份中的天数 Number 10 F 月份中的星期 Number 2 E 星期中的天数

第3篇 QML状态与变换_嵌入式职场的博客-程序员秘密

目录1、Cell.qml组件2、状态与变换3、代码讲解4、效果展示1、Cell.qml组件颜色拾色器设置:import QtQuick 2.0Item { id: container property alias cellColor: rectangle.color signal clicked(color cellColor) width: 40 height: 50 Rectangle { id:rec