PHP高级面试题大全(附带详细答案)

目录

1,zval详解(php5时期的)

2,zval详解(php7之后的,包括php7版本)

3,php的写时复制

4,php的数组原理(php5的hashtable原理)

5,php的数组原理(php7的hashtable原理)

6,hashtable扩容原理

7,哈希冲突的解决方法

8,php的垃圾回收机制(gc)

9,php怎么处理循环引用的问题?


1,zval详解(php5时期的)

/*这个是zval的实际结构,zval就是php中定义变量的容器,你申请一个变量就是创建一个zval
  对于数组,数组本身是一个zval,数组中的每个值也是一个zval
*value;         是值或者是地址,内容是值还是地址,要看type的值是什么
*refcount__gc;  计数,用于垃圾回收,
                $a = 'php'; //变量a的计数是1,不能被回收
                $b = $a; //变量a的计数是2,不能被回收(php是写时复制,变量a,b都是同一个zval)
                unset($b);//变量a的计数是1,不能被回收
                unset($a);//变量a的计数是0,可以被回收,不代表马上就回收,具体要看gc是否触发

*type;          类型,类型有NULL,LONG,DOUBLE,BOOL,ARRAY,OBJECT,STRING,
                IS_RESOURCE等类型
*is_ref__gc;    是否被引用(逻辑值),默认是0(没有引用),$b = &$a;变量a的is_ref__gc=1,被引用
*/
struct _zval_struct {
    zvalue_value value;     
    zend_uint refcount__gc;  
    zend_uchar type; 
    zend_uchar is_ref__gc;
};  

typedef struct _zval_struct zval;  //这个就是把这个结构起个别名

//这是个联合体
typedef union _zvalue_value {
    long lval;                //如果zval中的type是LONG,这个就是long的值      
    double dval;              //如果zval中的type是double,这个就是double的值  
    struct {                  //如果string,是struct的地址,struct里是字符串的地址和长度
        char *val;
        int len;
    } str;
    HashTable *ht;           //如果是array,就是hashtable的地址,因为数组就是hashtable实现的    
    zend_object_value obj;   //如果是object,是对象的结构体
} zvalue_value;

2,zval详解(php7之后的,包括php7版本)

struct _zval_struct {
    zend_value        value; //这个就是下面的那个_zend_value名的联合体
    union {
        struct {
            zend_uchar type,      /*变量类型,null,false,true,long,double,string,array等
                                    null,false,true他们只需要知道类型,不需要访问value*/
            zend_uchar type_flags,/*变量类型掩码,不同的类型会有不同的几种属性。比如当前类型是    
                                  否支持引用计数、是否支持写时复制。主要在内存管理时会用*/
            zend_uchar const_flags,//常量类型标记
            zend_uchar reserved
        } v;
        uint32_t type_info; 
    } u1;
    union {
        uint32_t     var_flags;
        uint32_t     next;             //哈希表中解决哈希冲突时用到
        uint32_t     cache_slot;           
        uint32_t     lineno;             
        uint32_t     num_args;           
        uint32_t     fe_pos;              
        uint32_t     fe_iter_idx;        
    } u2; //一些辅助值
};

typedef struct _zval_struct     zval;  //把这个结构体起个别名zval

//zval的value部分,如果是long,double里面的内容的就是值,否则就是其他类型的地址
typedef union _zend_value {
    zend_long         lval;    //存储的是值
    double            dval;    //存储的是值

    zend_refcounted  *counted; 
    zend_string      *str;     //string字符串,结构的指针
    zend_array       *arr;     //array数组,结构的指针
    zend_object      *obj;     //object对象,结构的指针
    zend_resource    *res;     //resource资源类型,结构的指针
    zend_reference   *ref;     //引用类型,通过&$var_name定义的
    zend_ast_ref     *ast;    
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

/*注意:php7的计数都是放到value里的,比如zend_string字符串结构体中,gc就是计数相关的数据
zend_array等结构中也有类似的
*/
struct _zend_string {
        zend_refcounted_h gc;
        zend_ulong        h;            
        size_t            len;
        char              val[1];
};

3,php的写时复制

在写入的时候才真正复制一份,这样做的好处是减少内存的使用。

//以php5时期的zval结构讲解,php7的zval结构不同,原理一样的
$a = 'php';   //创建一变量(结构是一个zval,type是字符串,refcount__gc等于1)
$b = $a;      //创建b变量,底层zval结构还是指向a的zval,这个zval的refcount__gc变成了2
$b = 'php7';  /*写时复制在这个时候发生(改变量b的值,但是变量a的值不变),创建了一个新的zval,值内容 
              是php7,原来那个a对应的zval内容不变,还是php,但是refcount__gc会减1,变成了1
              */

4,php的数组原理(php5的hashtable原理)

php的数组,在底层使用hashtable实现的

//hashtable结构
typedef struct _hashtable {
    uint nTableSize;         //hashtable表的大小,都是2的n次方,扩容都是2倍的扩,最小为8
    uint nTableMask;         //是一个掩码,用于快速计算索引的
    uint nNumOfElements;     //这个数组放入了多少元素,count($ar)返回的就是这个值
    ulong nNextFreeElement;  //下一个可用的数字索引,例如$ar[] = 'abc',索引就是这个确定的
    Bucket *pInternalPointer;//一个指针,在使用current,next,key,end等函数就是这个这个记录位置
    Bucket *pListHead;       //链表的头元素地址
    Bucket *pListTail;       //链表的尾元素地址
    Bucket **arBuckets;      //bucket *类型的数组,bucket是实际存储数据的容器,下面那个结构就是
    dtor_func_t pDestructor;
    zend_bool persistent;
    unsigned char nApplyCount;
    zend_bool bApplyProtection;
} HashTable;

/*这个结构是hashtable的的主要结构,存储数据的容器,数组有多少个元素,就会有多少个Bucket
 *对于数字型索引,直接使用h作为hash值,同时,arKey=NULL 且nKeyLength=0
 *对于字符串索引,arKey保存字符串key, nKeyLength保存该key的长度,h则是key的hash值
*/
typedef struct bucket {
    ulong h;           //数字索引值或者字符串的hash值
    uint nKeyLength;   //key的长度
    void *pData;
    void *pDataPtr;
    struct bucket *pListNext;  //整个hash表的,下一个元素(保证数据顺序)
    struct bucket *pListLast;  //整个hash表的,前一个元素(保证数据顺序)
    struct bucket *pNext;      //相同slot的链表的,下一个元素(hash冲突的时候,寻找数据)
    struct bucket *pLast;      //相同slot的链表的,前一个元素(hash冲突的时候,寻找数据)
    const char *arKey;         //key值
} Bucket;

5,php的数组原理(php7的hashtable原理)

typedef struct _Bucket {
    zval        val;  //值放到了zval中
    zend_ulong  h;   //hash值
    zend_string *key;//key值
} Bucket;
 
typedef struct _zend_array HashTable;
 
struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                    zend_uchar    flags,
                    zend_uchar    nApplyCount,
                    zend_uchar    nIteratorsCount,
                    zend_uchar    reserve)
        } v;
        uint32_t flags;
    } u;
    uint32_t nTableMask;      //哈希值掩码,等于nTableSize的负值(nTableMask = ~nTableSize + 1)
    Bucket *arData;           //存储元素数组,指向第一个Bucket,数组中每个元素都是Bucket
    uint32_t  nNumUsed;       //arData数组已经使用的数量,已用Bucket数,unset元素时,这个不变
    uint32_t  nNumOfElements; //哈希表已有元素数,unset元素时,这个会减1
    uint32_t  nTableSize;     //哈希表总大小,为2的n次方,最小值为8
    uint32_t  nInternalPointer;//内部的指针,用于HashTable遍历
    zend_long nNextFreeElement; //下一个可用的数值索引,如:arr[] = 1
    dtor_func_t pDestructor; // 析构函数
}

6,hashtable扩容原理

当哈希表中存储的键值对超过负载因子阈值时,就需要进行扩容操作。负载因子是指哈希表中当前存储的键值对数量与哈希表总容量之间的比值,默认的负载因子大小为0.75。扩容的目的是减少hash冲突的概率,提高访问和更新效率。

7,哈希冲突的解决方法

链接法:php的hashtable用的是这种方法,如果遇到hash冲突的时候,通过链表把hash冲突的数据弄成一个链表,查找的时候,定位到所在的slot后,遍历这个链表。
开放寻址法:在存入的时候,通过公式 f(key,n)取hash,其中n可以认为是[0,1,2,3....],f(key,0)的hash值寻找slot位,如果没有数据,就使用这个位置,如果有数据,在通过f(key,1)取hash计算slot位,直到找到位置为止。查询的时候也是一样,通过f(key,0)的hash找到slot位,如果有数据,比较key是否相等,相等就是要找的数据,如果不相等,在通过f(key,1)的hash找slot位,如果找到的slot为空,就是没有找到这个key对应的值,可以结束寻找。

8,php的垃圾回收机制(gc)

php.ini配置里的zend.enable_gc是控制gc是否打开,默认打开;

函数gc_enable() 和 gc_disable()在运行时来打开和关闭垃圾回收机制;gc_collect_cycles()强制收集所有现存的垃圾循环周期

每个内存对象(就是zval)都分配一个计数器,当内存对象被变量引用时,计数器+1;
当变量引用撤掉后(执行unset()后),计数器-1;
当计数器=0,且没有引用时,表明内存对象没有被使用,该内存对象可以进行销毁。

垃圾回收的目的不用程序员手动管理内存,提高了编程的安全性和效率,避免了内存溢出的发生。

是如果遇到循环引用,就会出现内存泄漏的情况。

9,php怎么处理循环引用的问题?

循环引用的这种情况,是在php5.3以后才有的解决方法。

//循环引用的过程(对象,数组才会有循环引用的情况)
$ar = [];           //会产生一个zval,类型是数组,计数(refcount__gc)是1
$ar['a'] = 'php';   //也会产生一个zval,类型是字符串,他的计数(refcount__gc)是1
$ar['b'] = &$ar;    /*也会产生一个zval,是一个引用类型,计数(refcount__gc)是1,
                      由于引用了$ar,$ar的计数(refcount__gc)变成了2,zval引用(is_ref__gc)是1,
                     */
unset($ar);         /*会把$ar的计数(refcount__gc)减1,次数(refcount__gc)计数是1,垃圾回收不会回    
                      收$ar
                      unset($ar)的本意是不用这个数组了,可以回收的,但是有循环引用,gc无法回收        
                      他,这就是一个内存泄漏                   
                    */

//----------------分割线------------------

//一个不存在循环引用的过程
$ar = [];           //会产生一个zval,类型是数组,计数(refcount__gc)是1
$ar['a'] = 'php';   //也会产生一个zval,类型是字符串,他的计数(refcount__gc)是1
$ar['b'] = 123;     //也会产生一个zval,计数(refcount__gc)是1,
unset($ar);         //会把$ar的计数(refcount__gc)减1,计数变成了0,直接回收                
                   

解决方法就是增加了一个缓冲区(默认长度是10000),unset某个变量后,如果计数(refcount__gc)大于0(如果等于0,就可以直接回收,肯定是垃圾),那他可能就是存在循环引用的情况,就把它放入这个缓冲区(肯定也是需要排重的),如果这个缓冲区满了,就开始进行判断。判断方式是遍历取出缓冲区的每个元素,然后遍历每个元素,对每个元素做unset模拟操作,这个元素模拟操作后,在回来查看refcount_gc,如果变成了0,就是垃圾,可以回收,否则就不是,不能进行回收,且需要把模拟的unset给反向加回去。


//遇到循环引用,gc是怎么处理的
$ar = [];         
$ar['a'] = 'php'; 
$ar['b'] = &$ar;
unset($ar);       /*unset($ar)后,$ar对应的zval的refcount__gc=1,大于0,可能存在循环引用,需要加        
                  入缓冲区
                  当缓冲区满了,取出$ar,然后遍历$ar的所有元素,模拟操作,    
                  unset($ar['a']),unset($ar['b'])(这步就是断开引用关系,会使$ar的 
                  refcount__gc减1),遍历完了后,在看$ar的refcount__gc已经变成0了,说明是循环引 
                  用,可以进行回收了

                  如果最后发现refcount__gc任然大于0,就不是循环引用,把原来所有元素refcount__gc 
                  的给还原回来
                  */
  

 ------------------------------------------推荐阅读----------------------------------------------------------------

PHP基础面试题大全(附带详细答案)

http,tcp,nginx相关的面试题

mysql面试题详解(含详细解析)

Hadoop面试题答案大全是一个广泛的主题,涵盖了Hadoop生态系统中各个组件的概念、架构、工作原理以及相关的技术细节。以下是一些常见的Hadoop面试题答案,供参考: 1. 什么是Hadoop? Hadoop是一个开源的分布式计算框架,用于存储和处理大规模数据集。它基于主从架构,使用HDFS存储数据,利用MapReduce进行数据处理和计算。 2. Hadoop的核心组件是什么? Hadoop的核心组件包括HDFS(Hadoop Distributed File System)和MapReduce。 3. 解释一下HDFS的工作原理。 HDFS将大规模数据集分割成小文件块,并将这些块存储在集群中的多个计算节点上。每个块都有多个副本,分布在不同的节点上,以实现数据的冗余和容错。HDFS通过Master/Slave架构管理文件的存储和访问。 4. 什么是MapReduce? MapReduce是一种编程模型,用于处理并行计算和大规模数据集。它将计算任务分解为两个阶段:Map和Reduce。Map阶段将输入数据切分成独立的片段并进行处理,然后Reduce阶段将Map的输出结果合并成最终的结果。 5. Hadoop的优点是什么? Hadoop具有以下优点: - 高可靠性和容错性:通过数据冗余和自动故障转移,能够处理节点故障。 - 高扩展性:能够处理大规模数据集,并随着数据量的增加进行水平扩展。 - 高效性:通过并行处理和数据本地化,提供高效的数据处理能力。 - 成本效益:使用廉价的硬件构建集群,并通过数据冗余提供容错性,降低了成本。 这些是一些常见的Hadoop面试题答案,但请注意面试可能会涉及更深入的技术问题和场景分析。在准备面试时,建议对Hadoop的各个组件、原理和应用有更深入的了解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

geegtb

只希望写的东西能够帮助到你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值