當前位置:首頁 » 編程語言 » 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是一個典型的圖形資料庫。

選擇哪一種數據模型?

數據模型有著各自的優缺點,它們適用於不同的領域。不管是選擇關系模型,還是非關系模型,都要根據實際應用的場景做出選擇。也許你會發現單一的數據模型不能滿足你的解決方案,許多大型應用可能需要集成多種數據模型。

熱點內容
phpecho換行 發布:2024-04-30 02:21:51 瀏覽:903
高中ftp 發布:2024-04-30 01:51:48 瀏覽:873
林秋楠手機的密碼是多少 發布:2024-04-30 01:46:31 瀏覽:276
python靜態類方法 發布:2024-04-30 01:30:28 瀏覽:462
zblogphpasp 發布:2024-04-30 01:27:35 瀏覽:137
宏程序自動編程軟體 發布:2024-04-30 01:15:01 瀏覽:417
vs添加編譯選項 發布:2024-04-30 01:06:10 瀏覽:614
編程紅碼 發布:2024-04-30 01:04:49 瀏覽:910
給數組賦值java 發布:2024-04-30 01:04:37 瀏覽:499
我的世界jave版如何開伺服器 發布:2024-04-30 01:02:34 瀏覽:902