当前位置:首页 » 编程语言 » php哈希表

php哈希表

发布时间: 2024-04-17 03:11:37

A. php面试题 memcache和redis的区别

Redis与Memcached的区别

传统Mysql+ Memcached架构遇到的问题

实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题:

1.MySQL需要不断进行拆库拆表,Memcached也需不断跟着扩容,扩容和维护工作占据大量开发时间。

2.Memcached与MySQL数据库数据一致性问题。

3.Memcached数据命中率低或down机,大量访问直接穿透到DB,MySQL无法支撑。

4.跨机房cache同步问题。

众多NoSQL百花齐放,如何选择

最近几年,业界不断涌现出很多各种各样的NoSQL产品,那么如何才能正确地使用好这些产品,最大化地发挥其长处,是我们需要深入研究和思考的
问题,实际归根结底最重要的是了解这些产品的定位,并且了解到每款产品的tradeoffs,在实际应用中做到扬长避短,总体上这些NoSQL主要用于解
决以下几种问题

1.少量数据存储,高速读写访问。此类产品通过数据全部in-momery 的方式来保证高速访问,同时提供数据落地的功能,实际这正是Redis最主要的适用场景。

2.海量数据存储,分布式系统支持,数据一致性保证,方便的集群节点添加/删除。

3.这方面最具代表性的是dynamo和bigtable 2篇论文所阐述的思路。前者是一个完全无中心的设计,节点之间通过gossip方式传递集群信息,数据保证最终一致性,后者是一个中心化的方案设计,通过类似一个分布式锁服务来保证强一致性,数据写入先写内存和redo log,然后定期compat归并到磁盘上,将随机写优化为顺序写,提高写入性能。

4.Schema free,auto-sharding等。比如目前常见的一些文档数据库都是支持schema-free的,直接存储json格式数据,并且支持auto-sharding等功能,比如mongodb。

面对这些不同类型的NoSQL产品,我们需要根据我们的业务场景选择最合适的产品。

Redis适用场景,如何正确的使用

前面已经分析过,Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-
backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用
Memcached,何时使用Redis呢?

如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:

1 Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

2 Redis支持数据的备份,即master-slave模式的数据备份。

3 Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

抛开这些,可以深入到Redis内部构造去观察更加本质的区别,理解Redis的设计。


Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别。Redis只会缓存所有的
key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability =
age*log(size_in_memory)”计
算出哪些key对应的value需要swap到磁盘。然后再将这些key对应的value持久化到磁盘中,同时在内存中清除。这种特性使得Redis可以

保持超过其机器本身内存大小的数据。当然,机器本身的内存必须要能够保持所有的key,毕竟这些数据是不会进行swap操作的。同时由于Redis将内存

中的数据swap到磁盘中的时候,提供服务的主线程和进行swap操作的子线程会共享这部分内存,所以如果更新需要swap的数据,Redis将阻塞这个
操作,直到子线程完成swap操作后才可以进行修改。

使用Redis特有内存模型前后的情况对比:
VM off: 300k keys, 4096 bytes values: 1.3G used
VM on: 300k keys, 4096 bytes values: 73M used
VM off: 1 million keys, 256 bytes values: 430.12M used
VM on: 1 million keys, 256 bytes values: 160.09M used
VM on: 1 million keys, values as large as you want, still: 160.09M used



从Redis中读取数据的时候,如果读取的key对应的value不在内存中,那么Redis就需要从swap文件中加载相应数据,然后再返回给请求方。

这里就存在一个I/O线程池的问题。在默认的情况下,Redis会出现阻塞,即完成所有的swap文件加载后才会相应。这种策略在客户端的数量较小,进行

批量操作的时候比较合适。但是如果将Redis应用在一个大型的网站应用程序中,这显然是无法满足大并发的情况的。所以Redis运行我们设置I/O线程
池的大小,对需要从swap文件中加载相应数据的读取请求进行并发操作,减少阻塞的时间。

如果希望在海量数据的环境中使用好Redis,我相信理解Redis的内存设计和阻塞的情况是不可缺少的。

补充的知识点:

memcached和redis的比较

1 网络IO模型

Memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,将连接描述
字pipe 传递给worker线程,进行读写IO, 网络层使用libevent封装的事件库,多线程模型可以发挥多核作用,但是引入了cache
coherency和锁的问题,比如,Memcached最常用的stats
命令,实际Memcached所有操作都要对这个全局变量加锁,进行计数等工作,带来了性能损耗。

(Memcached网络IO模型)

Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和select,
对于单纯只有IO操作来说,单线程可以将速度优势发挥到最大,但是Redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实
际会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞住的。

2.内存管理方面

Memcached使用预分配的内存池的方式,使用slab和大小不同的chunk来管理内存,Item根据大小选择合适的chunk存储,内
存池的方式可以省去申请/释放内存的开销,并且能减小内存碎片产生,但这种方式也会带来一定程度上的空间浪费,并且在内存仍然有很大空间时,新的数据也可
能会被剔除,原因可以参考Timyang的文章:http://timyang.net/data/Memcached-lru-evictions/

Redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎片,Redis
跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致swap也不会剔
除任何非临时数据(但会尝试剔除部分临时数据),这点上Redis更适合作为存储而不是cache。

3.数据一致性问题

Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题。 Redis没有提供cas 命令,并不能保证这点,不过Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断。

4.存储方式及其它方面

Memcached基本只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能

Redis除key/value之外,还支持list,set,sorted set,hash等众多数据结构,提供了KEYS

进行枚举操作,但不能在线上使用,如果需要枚举线上数据,Redis提供了工具可以直接扫描其mp文件,枚举出所有数据,Redis还同时提供了持久化和复制等功能。

5.关于不同语言的客户端支持

在不同语言的客户端方面,Memcached和Redis都有丰富的第三方客户端可供选择,不过因为Memcached发展的时间更久一些,目
前看在客户端支持方面,Memcached的很多客户端更加成熟稳定,而Redis由于其协议本身就比Memcached复杂,加上作者不断增加新的功能
等,对应第三方客户端跟进速度可能会赶不上,有时可能需要自己在第三方客户端基础上做些修改才能更好的使用。

根据以上比较不难看出,当我们不希望数据被踢出,或者需要除key/value之外的更多数据类型时,或者需要落地功能时,使用Redis比使用Memcached更合适。

关于Redis的一些周边功能

Redis除了作为存储之外还提供了一些其它方面的功能,比如聚合计算、pubsub、scripting等,对于此类功能需要了解其实现原
理,清楚地了解到它的局限性后,才能正确的使用,比如pubsub功能,这个实际是没有任何持久化支持的,消费方连接闪断或重连之间过来的消息是会全部丢
失的,又比如聚合计算和scripting等功能受Redis单线程模型所限,是不可能达到很高的吞吐量的,需要谨慎使用。

总的来说Redis作者是一位非常勤奋的开发者,可以经常看到作者在尝试着各种不同的新鲜想法和思路,针对这些方面的功能就要求我们需要深入了解后再使用。

总结:

1.Redis使用最佳方式是全部数据in-memory。

2.Redis更多场景是作为Memcached的替代者来使用。

3.当需要除key/value之外的更多数据类型支持时,使用Redis更合适。

4.当存储的数据不能被剔除时,使用Redis更合适。

谈谈Memcached与Redis(一)

1. Memcached简介

Memcached是以LiveJurnal旗下Danga Interactive公司的Bard
Fitzpatric为首开发的高性能分布式内存缓存服务器。其本质上就是一个内存key-value数据库,但是不支持数据的持久化,服务器关闭之后数
据全部丢失。Memcached使用C语言开发,在大多数像Linux、BSD和Solaris等POSIX系统上,只要安装了libevent即可使
用。在Windows下,它也有一个可用的非官方版本(http://code.jellycan.com/memcached/)。Memcached
的客户端软件实现非常多,包括C/C++, PHP, Java, Python, Ruby, Perl, Erlang,
Lua等。当前Memcached使用广泛,除了LiveJournal以外还有Wikipedia、Flickr、Twitter、Youtube和
WordPress等。

在Window系统下,Memcached的安装非常方便,只需从以上给出的地址下载可执行软件然后运行memcached.exe –d
install即可完成安装。在Linux等系统下,我们首先需要安装libevent,然后从获取源码,make && make
install即可。默认情况下,Memcached的服务器启动程序会安装到/usr/local/bin目录下。在启动Memcached时,我们可
以为其配置不同的启动参数。

1.1 Memcache配置

Memcached服务器在启动时需要对关键的参数进行配置,下面我们就看一看Memcached在启动时需要设定哪些关键参数以及这些参数的作用。

1)-p <num> Memcached的TCP监听端口,缺省配置为11211;

2)-U <num> Memcached的UDP监听端口,缺省配置为11211,为0时表示关闭UDP监听;

3)-s <file> Memcached监听的UNIX套接字路径;

4)-a <mask> 访问UNIX套接字的八进制掩码,缺省配置为0700;

5)-l <addr> 监听的服务器IP地址,默认为所有网卡;

6)-d 为Memcached服务器启动守护进程;

7)-r 最大core文件大小;

8)-u <username> 运行Memcached的用户,如果当前为root的话需要使用此参数指定用户;

9)-m <num> 分配给Memcached使用的内存数量,单位是MB;

10)-M 指示Memcached在内存用光的时候返回错误而不是使用LRU算法移除数据记录;

11)-c <num> 最大并发连数,缺省配置为1024;

12)-v –vv –vvv 设定服务器端打印的消息的详细程度,其中-v仅打印错误和警告信息,-vv在-v的基础上还会打印客户端的命令和相应,-vvv在-vv的基础上还会打印内存状态转换信息;

13)-f <factor> 用于设置chunk大小的递增因子;

14)-n <bytes> 最小的chunk大小,缺省配置为48个字节;

15)-t <num> Memcached服务器使用的线程数,缺省配置为4个;

16)-L 尝试使用大内存页;

17)-R 每个事件的最大请求数,缺省配置为20个;

18)-C 禁用CAS,CAS模式会带来8个字节的冗余;

2. Redis简介

Redis是一个开源的key-value存储系统。与Memcached类似,Redis将大部分数据存储在内存中,支持的数据类型包括:字
符串、哈希表、链表、集合、有序集合以及基于这些数据类型的相关操作。Redis使用C语言开发,在大多数像Linux、BSD和Solaris等
POSIX系统上无需任何外部依赖就可以使用。Redis支持的客户端语言也非常丰富,常用的计算机语言如C、C#、C++、Object-C、PHP、
Python、Java、Perl、Lua、Erlang等均有可用的客户端来访问Redis服务器。当前Redis的应用已经非常广泛,国内像新浪、淘
宝,国外像Flickr、Github等均在使用Redis的缓存服务。

Redis的安装非常方便,只需从http://redis.io/download获取源码,然后make && make

install即可。默认情况下,Redis的服务器启动程序和客户端程序会安装到/usr/local/bin目录下。在启动Redis服务器时,我们
需要为其指定一个配置文件,缺省情况下配置文件在Redis的源码目录下,文件名为redis.conf。

B. thinkphp redis 怎么选择数据库

1、redis 中的每一个数据库,都由一个 redisDb 的结构存储。其中,redisDb.id 存储着 redis 数据库以整数表示的号码。redisDb.dict 存储着该库所有的键值对数据。redisDb.expires 保存着每一个键的过期时间。

2、当redis 服务器初始化时,会预先分配 16 个数据库(该数量可以通过配置文件配置),所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中。当我们选择数据库 select number 时,程序直接通过 redisServer.db[number] 来切换数据库。有时候当程序需要知道自己是在哪个数据库时,直接读取 redisDb.id 即可。

3、既然我们知道一个数据库的所有键值都存储在redisDb.dict中,那么我们要知道如果找到key的位置,就有必要了解一下dict 的结构了:

typedef struct dict {

// 特定于类型的处理函数
dictType *type;

// 类型处理函数的私有数据
void *privdata;

// 哈希表(2个)
dictht ht[2];

// 记录 rehash 进度的标志,值为-1 表示 rehash 未进行
int rehashidx;

// 当前正在运作的安全迭代器数量
int iterators;
} dict;
由上述的结构可以看出,redis 的字典使用哈希表作为其底层实现。dict 类型使用的两个指向哈希表的指针,其中 0 号哈希表(ht[0])主要用于存储数据库的所有键值,而1号哈希表主要用于程序对 0 号哈希表进行 rehash 时使用,rehash 一般是在添加新值时会触发,这里不做过多的赘述。所以redis 中查找一个key,其实就是对进行该dict 结构中的 ht[0] 进行查找操作。

4、既然是哈希,那么我们知道就会有哈希碰撞,那么当多个键哈希之后为同一个值怎么办呢?redis采取链表的方式来存储多个哈希碰撞的键。也就是说,当根据key的哈希值找到该列表后,如果列表的长度大于1,那么我们需要遍历该链表来找到我们所查找的key。当然,一般情况下链表长度都为是1,所以时间复杂度可看作o(1)。

二、当redis 拿到一个key 时,如果找到该key的位置。

了解了上述知识之后,我们就可以来分析redis如果在内存找到一个key了。

1、当拿到一个key后, redis 先判断当前库的0号哈希表是否为空,即:if (dict->ht[0].size == 0)。如果为true直接返回NULL。

2、判断该0号哈希表是否需要rehash,因为如果在进行rehash,那么两个表中者有可能存储该key。如果正在进行rehash,将调用一次_dictRehashStep方法,_dictRehashStep 用于对数据库字典、以及哈希键的字典进行被动 rehash,这里不作赘述。

3、计算哈希表,根据当前字典与key进行哈希值的计算。

4、根据哈希值与当前字典计算哈希表的索引值。

5、根据索引值在哈希表中取出链表,遍历该链表找到key的位置。一般情况,该链表长度为1。

6、当 ht[0] 查找完了之后,再进行了次rehash判断,如果未在rehashing,则直接结束,否则对ht[1]重复345步骤。

到此我们就找到了key在内存中的位置了。

C. php的URL传参,通过URL传!

PHPURL传参是向URL里面添加字符串的方式来进行传递的。
例:
index.php?id=100&name=test
上面这个url传递了id为100,name为test的传,可以通过$_GET['id']和$_GET['name']分别获取这两个值。

D. php-红黑树、散列表、跳表理解入门

就是把链表的结构稍加改造,这种数据结构叫

为了提升链表的查询效率,怎么让链表支持类似‘数组’那样的‘二分’算法呢

跳表是一个各方面性能都比较优秀的 动态数据结构 ,可以支持快速地插入、删除、查找操作,写起来也不复杂,甚至可以替代红黑树。

Redis 中的有序集合(Sorted Set)就是用跳表来实现的。
那 Redis 为什么会选择用跳表(和散列表)来实现有序集合呢? 为什么不用红黑树呢?这个问题一会在回答,先看看跳表的数据结构

其实概念很简单,就是在链表上加上了

当我们在不停插入数据,如果我们不更新索引,可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表还会退化成单链表。
红黑树、AVL 树这样平衡二叉树,是通过左右旋的方式保持左右子树的大小平衡,而跳表是通过 随机函数 来维护平衡性。

插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是, 按照区间来查找数据这个操作,红黑树的效率没有跳表高。

对于按照区间查找数据这个操作,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。

Redis 键值构建一个散列表,这样按照 key 来删除、查找一个成员对象的时间复杂度就变成了 O(1)。同时,借助跳表结构,其他操作也非常高效。

散列表的英文叫“Hash Table”,我们平时也叫它“哈希表”或者“Hash 表”



散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系 f,使得每个关键字 key 对应一个存储位置 f(key)。查找时根据这个对应关系匠互给定的 key 的映射 f(key)

这种关系 f 称为散列函数(又称哈希函数)。散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表。那么关键字对应的记录存储位置称为散列地址。

散列函数的构造方法特点就是:计算简单、散列地址分布均匀

大家一定听说过 hash 碰撞。就是2个不同的 key 对应着不同的 f 关系。但这是几乎不可能的,即便像业界着名的MD5、SHA、CRC等哈希算法,也无法完全避免这种散列冲突。而且,因为数组的存储空间有限,也会加大散列冲突的概率。

我们只能通过其它途径来寻找方法。我们常用的散列冲突解决方法有两类,开放寻址法(open addressing)和链表法(chaining)。

所谓的开放寻址法就是一但发生了冲突,就去寻找下一个空的散地址,只要散列表足够大,空的散列表地址总能找到,并将记录存入。

链地址法又称链表法,其实当发生冲突时存入链表,如下图很容易就可以看明白。此时,已经不存在什么冲突地址的问题,无论有多少冲突,都只是在当前位置给单链表增加结点的问题。

这种不常见,就是把冲突的单独找个地方。

顾名思义,红黑树中的节点,一类被标记为黑色,一类被标记为红色。除此之外,一棵红黑

平衡二叉树 是一种二叉排序树,其中每一个节点的左子树和右子树的高度不能大于 1

红黑树是一种平衡二叉查找树。它是为了解决普通二叉查找树在数据更新的过程中,复杂度退化的问题而产生的。红黑树的高度近似 log2n,所以它是近似平衡,插入、删除、查找操作的时间复杂度都是 O(logn)。

平衡二叉查找树其实有很多,比如,Splay Tree(伸展树)、Treap(树堆)等,但是我们提到平衡二叉查找树,听到的基本都是红黑树。
红黑树在众多里面,表现的最为平衡。
“近似平衡”就等价为性能不会退化得太严重。

一棵红黑树还需要满足这样几个要求:

看到这里你会很头大,什么黑的红的,完全不懂。赋上连接,有时间在看

散列表 :插入删除查找都是O(1), 是最常用的,但其缺点是不能顺序遍历(存入的数据是无顺序的)以及扩容缩容的性能损耗。适用于那些不需要顺序遍历,数据更新不那么频繁的。
散列表总和链表、跳表一起出现组合使用。

跳表 :插入删除查找都是O(logn), 并且能顺序遍历。缺点是空间复杂度O(n)。适用于不那么在意内存空间的,其顺序遍历和区间查找非常方便。
跳表还可以和散列表组合让删除、查找一个成员对象操作变为O(1),也就是说利用了散列表查找速度,跳表的顺序结构

红黑树 :插入删除查找都是O(logn), 中序遍历即是顺序遍历,稳定。缺点是难以实现,去查找不方便。其实跳表更佳,但红黑树已经用于很多地方了。

E. PHP命令执行PHP脚本,结束之前,内存会回收吗


再详细说下问题:
unix下,用php命令来执行php脚本,在php结束之前,内存有没有机会被回收?新的GC算法有没有机会被调用?
出现这个问题,是因为线上有个离线数据导入脚本,需要把几千万行数据筛选入库,发现,在执行过程中,到达一定程度,就会抛出内存使用超过最大值。
1Fatalerror:
那第一想到的就是程序是不是有什么bug,造成内存超出,看了半天没有发现问题,于是,突然出现了这个疑问。那要解决这个疑问,最好的办法就去翻源码吧。
在之前我这么说:
都知道,PHP5.3有了新的垃圾回收机制:GC,它不是重点,不谈它的原理。
经过翻阅PHP源码,发现,调用这个的时机是在main/main.c::php_request_shutdown这个函数中,
12/*7.Shutdownscanner/executor/compilerandrestoreinientries*/zend_deactivate(TSRMLS_C);
php_request_shutdown,通过名字就能看出,它是在php请求结束的时候执行的,在这里会执行gc_collect_cycles来清理内存。

其实这句话是没错,但它只针对于SAPI接口(之前我就错在这个地方。),在用PHP命令执行php脚本的时候,是不会执行这个php_request_shutdown的。
那回到最初的问题,过程中到底有没有执行GC呢?
为了更直观有效的知道答案,我选择了最BT,最暴力的方法,拦截gc_collect_cycles,输出error_log到文件,只要执行了,
那肯定会输出log来。
重新编译PHP后,果不其然,符合官方的说法,只要buckets满超过默认值1000,就会启动GC来清理没用的内存,防止内存泄露。
那问“什么时间触发的GC呢?”,答“buckets超过1000的时候啊”,这不屁话嘛,要的是真真正正的执行流程,so。。不断的debug,
不断的grep,不断的step,不断的C+T,终于搞清楚了。下面就来根据官方的说法详细谈谈,PHP到底是怎么触发的。
有一点要注意,PHP的命令入口和sapi接口的入口是不同的,我就载在这个地方,以为都公用一个。
测试代码以官方文档为例:
1234567891011121314<?phpclassFoo{public$var='3.1415962654';}for($i=0;$i<=1000000;$i++){$a=newFoo;$a->self=$a;}echomemory_get_peak_usage()," ";?>
这样的代码,在PHP5.3之前,肯定会造成大量的内存泄露,不过,谁在开发时又能开发出这么变态的代码来?除非这个人很变态。^.*
那PHP的命令入口是什么?流程又是什么?
主要函数流程如下:
入口main函数(sapi/cli/php_cli.c)==》php_execute_script(main/main.c)==>zend_execute_scripts(Zend/zend.c)==>execute(Zend/zend_vm_execute.h)
调用GC的地方在execute里。
简单描述下这个过程,
main是入口,它的作用是根据我们传递的参数做不同的设置,最后会把我们的php脚本作为一个zend_file_handle指针传递给
php_execute_script函数,zend_file_handle其实就是把FILE*做了一下封装,保存了一些其他的文件信息。
php_execute_script会做一些文件检查工作,把php脚本加到哈希表included_files中。
php_execute_scripts会执行zend_compile_file函数来解释我们写的PHP代码,最后执行execute。
应该都知道Zend把脚本解析完会生成op代码保存到哈希表:active_op_array中,execute会逐个执行每个op,
op基本上都对应一个ZEND_ASSIGN_*_HANDLER这样的一个宏,它就保存在active_op_array->opline->handlers中。
在进入到execute之后:
首先初始化execute_data,它保存了很多重要信息,上下文信息,然后调用ZEND_VM_SET_OPCODE宏,
把execute_data->opline的指针指向active_op_array->opline->handlers。
之后,execute会执行一个while循环,逐条执行opline:
(1){intret;#ifdefZEND_WIN32if(EG(timed_out)){zend_timeout(0);}#endifif((ret=EX(opline)->handler(execute_dataTSRMLS_CC))>0){switch(ret){case1:EG(in_execution)=original_in_execution;return;case2:op_array=EG(active_op_array);gotozend_vm_enter;case3:execute_data=EG(current_execute_data);default:break;}}}
每个handlers都会执行一个宏:ZEND_VM_NEXT_OPCODE(),它意思就是跳到下一个Opline,这样就能逐条执行了。
最后跟踪上面的PHP代码会执行ZEND_ASSIGN_SPEC_CV_VAR_HANDLER这个宏,它是干嘛的?他就是变量赋值
下面代码执行的操作:
1234classA{}$a=newA();
这里就会执行这个宏。
在这个宏里有段代码:
_FASTCALLZEND_ASSIGN_SPEC_CV_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS){zend_op*opline=EX(opline);zend_free_opfree_op2;zval*value=_get_zval_ptr_var(&opline->op2,EX(Ts),&free_op2TSRMLS_CC);zval**variable_ptr_ptr=_get_zval_ptr_ptr_cv(&opline->op1,EX(Ts),BP_VAR_WTSRMLS_CC);if(IS_CV==IS_VAR&&!variable_ptr_ptr){if(zend_assign_to_string_offset(&EX_T(opline->op1.u.var),value,IS_VARTSRMLS_CC)){if(!RETURN_VALUE_UNUSED(&opline->result)){EX_T(opline->result.u.var).var.ptr_ptr=&EX_T(opline->result.u.var).var.ptr;ALLOC_ZVAL(EX_T(opline->result.u.var).var.ptr);INIT_PZVAL(EX_T(opline->result.u.var).var.ptr);ZVAL_STRINGL(EX_T(opline->result.u.var).var.ptr,Z_STRVAL_P(EX_T(opline->op1.u.var).str_offset.str)+EX_T(opline->op1.u.var).str_offset.offset,1,1);}}elseif(!RETURN_VALUE_UNUSED(&opline->result)){AI_SET_PTR(EX_T(opline->result.u.var).var,EG(uninitialized_zval_ptr));PZVAL_LOCK(EG(uninitialized_zval_ptr));}}else{value=zend_assign_to_variable(variable_ptr_ptr,value,0TSRMLS_CC);if(!RETURN_VALUE_UNUSED(&opline->result)){AI_SET_PTR(EX_T(opline->result.u.var).var,value);PZVAL_LOCK(value);}}/*zend_assign_to_variable()alwaystakescareofop2,neverfreeit!*/if(free_op2.var){zval_ptr_dtor(&free_op2.var);};ZEND_VM_NEXT_OPCODE();}
free_op2.var保存的是newA的对象.
free_op2.var这个是哪儿来的呢?
在整个execute期间,维持一个execute_data结构,里面有个Ts指针
1union_temp_variable*Ts;
它用来保存一些临时的变量信息,比如newA(),这个会保存到Ts链表里,
opline->op2.u.var这个里面保存了此临时变量所在的位置,然后Ts+这个值是一个zval*指针,它就保存了newA产生的对象.
在代码中
1if(free_op2.var){zval_ptr_dtor(&free_op2.var);};
zval_ptr_dtor会根据free_op2.var的值执行到Zend/zend_execute_API.c::_zval_ptr_dtor函数中,
_APIvoid_zval_ptr_dtor(zval**zval_ptrZEND_FILE_LINE_DC)/*{{{*/{zval*zv=*zval_ptr;#ifDEBUG_ZEND>=2printf("Recingrefcountfor%x(%x):%d->%d ",*zval_ptr,zval_ptr,Z_REFCOUNT_PP(zval_ptr),Z_REFCOUNT_PP(zval_ptr)-1);#endifZ_DELREF_P(zv);if(Z_REFCOUNT_P(zv)==0){TSRMLS_FETCH();if(zv!=&EG(uninitialized_zval)){GC_REMOVE_ZVAL_FROM_BUFFER(zv);zval_dtor(zv);efree_rel(zv);}}else{TSRMLS_FETCH();if(Z_REFCOUNT_P(zv)==1){Z_UNSET_ISREF_P(zv);}GC_ZVAL_CHECK_POSSIBLE_ROOT(zv);}}
GC_ZVAL_CHECK_POSSIBLE_ROOT(zv);
它就是最终GC算法执行的地方.
gc_collect_cycles就在这个宏中执行了..
所以..
回到上面的问题,
php无论在SAPI接口或命令端,都会执行GC算法来进行垃圾内存回收.

F. 当前主流的数据库系统通常采用哪几种模型

目前最主流的sql server、oracle、mysql、db2都是关系型数据库。随着社交网站、视频网站等互联网新业务模式的兴起,各种非关系数据库模型也在不断涌现。

以下是的:
数据模型概述

1.关系模型

关系模型使用记录(由元组组成)进行存储,记录存储在表中,表由架构界定。表中的每个列都有名称和类型,表中的所有记录都要符合表的定义。SQL是专门的查询语言,提供相应的语法查找符合条件的记录,如表联接(Join)。表联接可以基于表之间的关系在多表之间查询记录。

表中的记录可以被创建和删除,记录中的字段也可以单独更新。

关系模型数据库通常提供事务处理机制,这为涉及多条记录的自动化处理提供了解决方案。

对不同的编程语言而言,表可以被看成数组、记录列表或者结构。表可以使用B树和哈希表进行索引,以应对高性能访问。

2.键值存储

键值存储提供了基于键对值的访问方式。

键值对可以被创建或删除,与键相关联的值可以被更新。

键值存储一般不提供事务处理机制。

对不同的编程语言而言,键值存储类似于哈希表。对此,不同的编程语言有不同的名字(如,Java称之为“HashMap”,Perl称之为“hash”,Python称之为“dict”,PHP称之为“associative array”),C++则称之为“boost::unordered_map<...>”。

键值存储支持键上自有的隐式索引。

键值存储看起来好像不太有用,但却可以在“值”上存储大量信息。“值”可以是一个XML文档,一个JSON对象,或者其它任何序列化形式。

重要的是,键值存储引擎并不在意“值”的内部结构,它依赖客户端对“值”进行解释和管理。

3.文档存储

文档存储支持对结构化数据的访问,不同于关系模型的是,文档存储没有强制的架构。

事实上,文档存储以封包键值对的方式进行存储。在这种情况下,应用对要检索的封包采取一些约定,或者利用存储引擎的能力将不同的文档划分成不同的集合,以管理数据。

与关系模型不同的是,文档存储模型支持嵌套结构。例如,文档存储模型支持XML和JSON文档,字段的“值”又可以嵌套存储其它文档。文档存储模型也支持数组和列值键。

与键值存储不同的是,文档存储关心文档的内部结构。这使得存储引擎可以直接支持二级索引,从而允许对任意字段进行高效查询。支持文档嵌套存储的能力,使得查询语言具有搜索嵌套对象的能力,XQuery就是一个例子。MongoDB通过支持在查询中指定JSON字段路径实现类似的功能。

4.列式存储

如果翻转数据,列式存储与关系存储将会非常相似。与关系模型存储记录不同,列式存储以流的方式在列中存储所有的数据。对于任何记录,索引都可以快速地获取列上的数据。

Map-rece的实现Hadoop的流数据处理效率非常高,列式存储的优点体现的淋漓极致。因此,HBase和Hypertable通常作为非关系型数据仓库,为Map-rece进行数据分析提供支持。

关系类型的列标对数据分析效果不好,因此,用户经常将更复杂的数据存储在列式数据库中。这直接体现在Cassandra中,它引入的“column family”可以被认为是一个“super-column”。

列式存储支持行检索,但这需要从每个列获取匹配的列值,并重新组成行。

5.图形数据库

图形数据库存储顶点和边的信息,有的支持添加注释。

图形数据库可用于对事物建模,如社交图谱、真实世界的各种对象。IMDB(Internet Movie Database)站点的内容就组成了一幅复杂的图像,演员与电影彼此交织在一起。

图形数据库的查询语言一般用于查找图形中断点的路径,或端点之间路径的属性。Neo4j是一个典型的图形数据库。

选择哪一种数据模型?

数据模型有着各自的优缺点,它们适用于不同的领域。不管是选择关系模型,还是非关系模型,都要根据实际应用的场景做出选择。也许你会发现单一的数据模型不能满足你的解决方案,许多大型应用可能需要集成多种数据模型。

热点内容
安卓光遇版本怎么看 发布:2024-11-28 06:29:52 浏览:870
oraclesql加密 发布:2024-11-28 06:29:15 浏览:900
图片缓存工具 发布:2024-11-28 06:29:15 浏览:962
有文件夹删不掉怎么办 发布:2024-11-28 06:22:50 浏览:526
python3怎么用 发布:2024-11-28 06:10:58 浏览:255
安卓盒子内存小怎么办 发布:2024-11-28 06:10:57 浏览:809
车企减配哪些配置好 发布:2024-11-28 05:55:18 浏览:674
传说对决上传照片 发布:2024-11-28 05:49:18 浏览:465
解压笔怎么画 发布:2024-11-28 05:49:17 浏览:518
预编译查询 发布:2024-11-28 05:32:35 浏览:313