會用CMap了,其他的基本也就會了,很容易理解。
映射表類(CMap)是MFC集合類中的一個模板類,也稱作為“字典”,就像一種只有兩列的表格,一列是關鍵字,一列是資料項目,它們是一一對應的。關鍵字
是唯一的,給出一個關鍵字,映射表類會很快找到對應的資料項目。映射表的尋找是以雜湊表的方式進行的,因此在映射表中尋找數值項的速度很快。舉個例子來說
吧,公司的所有職員都有一個工號和自己的姓名,工號就是姓名的關鍵字,給出一個工號,就可以很快的找到相應的姓名。映射類最適用於需要根據關鍵字進行快速
檢索的場合。
常用CMap:
CMapWordToPtr 儲存void指標,關鍵字為WORD
CMapPtrToWord 儲存WORD,關鍵字為void指標
CMapPtrToPtr 儲存void指標,關鍵字為其它void指標
CMapWordToOb 儲存CObject指標,關鍵字為WORD
CMapStringToOb 儲存CObject指標,關鍵字為字串
CMapStringToPtr 儲存void指標,關鍵字為字串
CMapStringToString 儲存字串,關鍵字為字串
一、 Map的基本知識
映射(Map),又稱為字典(Dictionary),是由關鍵字(Key)及其對應的元素值(Value)所組成的元素單元(Element)的表單式集合。
通常,對於Map而言,使用給定的Key,可以迅速地從資料格集合中檢索到相應的元素。因此,在需要對大量資料進行尋找操作而尋找的效能又佔據重要地位的
場合,Map無疑是一種較理想的容器。譬如,在MFC中,使用Map來實現HandleMaps(控制代碼映射),以及其他的一些內部資料結構。同時,MFC
也提供了公用Map類。使用公用Map類,MFC程式員可以輕易地高效地根據自身的需求實現程式中自訂的映射。
通常,當一個Map對象被刪除時,或者,當其中的元素被移除時,關鍵字和元素值也將被完全刪除。
從資料結構的角度分析,有關Map的典型操作有:
1、向Map中插入具有給定關鍵字的元素單元。
2、在Map中尋找具有給定關鍵字的元素單元。
3、在Map中刪除具有給定關鍵字的元素單元。
4、枚舉(遍曆)Map中的所有元素單元。
MFC中的各種Map實現,都提供了實現上述操作的成員函數。為了方便討論,我們以CMap為代表,進行講解。
一旦你已經向Map中插入了一個關鍵字-元素值組合對(Key-Value pair)單元,就可以利用關鍵字訪問Map,從而有效地檢索、添加或者刪除元素單元,也可以遍曆Map中的所有單元。
對MFC中的CMap等,除了關鍵字存取方法之外,還有另一種不同的類型--POSITION,也可以作為訪問元素單元的輔助方式,可以使用一個
POSITION來"記住"一個元素單元或者對Map進行枚舉操作。你可能認為這種使用POSITION實現的遍曆等同於使用關鍵字來進行的Map遍曆,
事實上並非如此,確切的說,兩種檢索的等價性是不確定的。
MFC中的提供了基於模板的CMap類。利用CMap模板類,可以處理特定的資料類型,例如使用者自訂的類或結構體等。同時,MFC也提供了基於指定資料類型的非模板類,其中包括:
| 類名 |
關鍵字類型 |
元素實值型別 |
| CMapWordToPtr |
WORDS |
Void pointers |
| CMapPtrToWord |
Void |
pointers WORDS |
| CMapPtrToPtr |
Void pointers |
Void pointers |
| CMapWordToOb |
WORDS |
Objects |
| CMapStringToOb |
Strings |
Objects |
| CMapStringToPtr |
Strings |
Void pointers |
| CMapStringToString |
Strings |
String |
二、 Map的工作原理
使用Map的最大優勢是它的快速尋找的優秀效能,而取得最優效能的關鍵在於盡量使得在檢
索周期內所需進行的元素檢查(比對)次數達到最少。順序尋找的效能是最差的,因為如果使用順序尋找演算法在包含n個元素單元的Map中尋找某個元素,可能
(最壞情況下)需要進行n次獨立的比較運算。
二元尋找(折中尋找)的效能表現要稍好一些,可是,一個不容忽視的問題是--二元尋找要
求待查序列為有序排列,這無疑會降低Map自身的操作靈活性。在我們的理解中,所謂的最佳演算法應當是不論元素單元數目的多少,也不論元素是以什麼順序進行
排列,尋找過程都無需任何額外的比對操作,而能夠僅僅通過簡單的計算方法,就可以直接指向最終的相應元素的快速、高效演算法。這聽起來有些玄乎,但事實上,
這種演算法的確是有可能實現的(而且,我相信,Map可以做得到)。
在MFC的CMap及其相關的Map類中,只要對Map進行正確設定,Lookup函數通常能夠一次到位的尋找到任意元素,而很少需要進行兩次或者三次以上的尋找比對。
那麼,這種高效的尋找是如何?的呢?
不失一般性,以MFC中的CMap模板類為例。在Map被建立之後(通常是恰恰在第一個元素被插入之前的瞬間),系統會為一個指向CAssoc結構體的指標數組的雜湊表分配記憶體。MFC使用CAssoc結構體描述元素值和關鍵字的組合對。
CAssoc結構體描述如下:
| struct CAssoc { CAssoc* pNext; UINT nHashValue; CString key; CString value; }; |
無論何時,只要有一個元素值-關鍵字單元被加入到Map中,就會隨之建立一個新的CAssoc結構體,並根據單元中的關鍵字的實際值來計算出相應的雜湊
值。同時,拷貝一個指向CAssoc結構體的指標並將其插入到雜湊表中索引值為i的位置中。其中,i的計算公式如下:
i=nHashValue%nHushTableSize
式中,nHashValue是由關鍵字Key的實際值計算出來的雜湊值;nHashTableSize是雜湊表中元素的數目(預設情況下,雜湊表的大小為17)。
如果在雜湊表中的索引值為i的位置已經容納了一個CAssoc指標,那麼MFC將建立一個單獨的CAssoc結構體的鏈表(List),鏈表中的第一個
CAssoc結構體的地址被儲存到雜湊表中,而將第二個CAssoc結構體的地址儲存到前一個CAssoc結構體的pNext域,以此類推。展示了哈
希表的一種可能實現情況,在該雜湊表中,共有10個元素,其中5個元素地址分別唯一的儲存,另外5個分別儲存在長度為2和3的兩個鏈表中。
調用一個Map的Lookup()函數時,MFC根據輸入的關鍵字的實際值計算相應的雜湊值,然後使用前面提到的公式將雜湊值轉換為索引值,並從雜湊表中的相應位置檢索CAssoc指標。
理想情況下,該位置只包含一個CAssoc指標,而非CAssoc指標鏈表。如果事實情況真如同我們所期望的那樣,單一地址對應單一CAssoc指標,
那麼,元素單元將能夠被一次尋找到位,並直接讀出;如果從雜湊表中檢索到的是CAssoc鏈表的指標頭地址,則MFC順序比對鏈表元素CAssoc結構所
包含的關鍵字,直至尋找到正確結果。但是,正如我們先前所討論的那樣,只要正確設定Map,鏈表中的元素一般就不會超過三個,這就意味著,尋找通常可以在
三次元素比對操作之內完成。
三、 最佳化尋找效率
在MFC的Map中,尋找效能主要依賴於兩個因素:
1、雜湊表的大小
2、儘可能產生唯一雜湊值的優異演算法
雜湊表的大小對於Map的尋找效能而言,是非常重要的。舉個簡單的例子,如果有一個Map要容納1000個元素單元,但是雜湊表卻只能提供17個存放
CAssoc指標的空間,那麼,即使是最佳情況,雜湊表中的每個CAssoc鏈表中也將包含58或59個CAssoc結構體,自然,在這種情況下,尋找性
能將受到嚴重阻礙。
雜湊演算法亦是影響尋找效率的重要因素之一。如果所使用的雜湊演算法只能產生少量的不同雜湊值(從而也只能產生少量的不同的雜湊表索引值),尋找效能也同樣將被降低。
最佳化Map尋找效能的最有效途徑是儘可能的增大雜湊表以降低因索引值相同而產生衝突的可能。微軟推薦將雜湊表的大小設定為Map中所儲存元素數目的110% ~120%,以使得Map的應用效能在記憶體消耗和尋找效率之間取得相對平衡。
在MFC中,指定雜湊表大小,可調用InitHashTable()函數:
map.InitHashTable(1200);
式中,假設Map中要儲存1000個元素,按照微軟公司的推薦,將雜湊表的大小擴充到實際儲存元素數目的120%,即設定Map大小為1200。
從統計學上考慮,實用奇數作為雜湊表的大小也將有助於減少衝突的發生。因此,初始化一個儲存1000個元素的雜湊表的InitHashTable()函數可以如下形式使用:
map.InitHashTable(1201);
同時,在InitHashTable()函數的調用時機上,應該注意的是,該函數應當在map包含有任何元素之前使。如果map中已經包含了一個或者更多的元素,那麼,重新改變map的大小,將會引發斷言(Assertion)錯誤。
儘管MFC中所使用的雜湊演算法能夠適應於大多數場合,但如果您真的有所需要,或者,只要你願意,使用者也可以使用自己的演算法來取代原有的演算法。對於一個輸
入的關鍵字的值,要計算出它的雜湊值,MFC通常調用一個通用範本函數HashKey(),對於大多數資料類型而言,HashKey()函數是以下面的方
式實現的:
AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key) { //一般情況的預設演算法。 return ((UINT)(void*)(DWORD)key) >> 4; } 但對於字串而言,其具體的實現方式如下: UINT AFXAPI HashKey(LPCWSTR key) // Unicode編碼字串 { UINT nHash = 0; while (*key) nHash = (nHash<<5) + nHash + *key++; return nHash; } UINT AFXAPI HashKey(LPCSTR key) // ANSI編碼字串 { UINT nHash = 0; while (*key) nHash = (nHash<<5) + nHash + *key++; return nHash; } |
要實現對應於特定資料類型的使用者自訂雜湊演算法,您可以使用上述的字串版本的HashKey()函數作為參考,寫一個類似的特定類型的HashKey()函數。
四、 使用MFC中的CMap類
有關MFC中的CMap類的概況,上面的文欄位落中已經陸續提及,在此不再贅言。下面,列出CMap類的基本成員函數,並通過一個簡短的程式片段來粗略地示範CMap類的使用方法。
建構函式:
操作:
| Lookup |
通過給定的關鍵字尋找相應的元素值。 |
| SetAt |
向Map中插入一個元素單元;若存在匹配鍵字,則替代之。 |
| operator [] |
向Map中插入一個元素 -SetAt的子操作 |
| RemoveKey |
移除由關鍵字標示的元素單元 |
| RemoveAll |
移除Map中的所有元素單元 |
| GetStartPosition |
返回第一個元素單元的位置 |
| GetNextAssoc |
讀取下一個元素單元 |
| GetHashTableSize |
返回雜湊表的大小(元素單元的數目) |
| InitHashTable |
初始化雜湊表,並指定它的大小 |
狀態:
| GetCount |
返回Map中元素的數目 |
| IsEmpty |
檢查Map是否為空白(無元素單元) |
應用執行個體如下:
CMap myMap; //初始化雜湊表,並指定其大小(取奇數) 。MyMap.InitHashTable(257); //向myMap中添加元素單元。 for (int i=0;i < 200;i++) myMap.SetAt( i, CPoint(i, i) ); // 刪除實際值為偶數的關鍵字所對應的的元素單元。 POSITION pos = myMap.GetStartPosition(); int nKey; CPoint pt; while (pos != NULL) { myMap.GetNextAssoc( pos, nKey, pt ); if ((nKey%2) == 0) myMap.RemoveKey( nKey ); } #ifdef _DEBUG afxDump.SetDepth( 1 ); afxDump << "myMap: " << &myMap << "\n"; #endif |
在上面的應用程式片段中,我們可以瞭解有關CMap類的在通常情況下的使用方法。
1、首先我們使用CMap模板類來定義一個執行個體--myMap對象。
2、緊接著要做的是對myMap對象的雜湊表的大小進行初始化設定。此時,應該先對myMap可能的容量需求進行估計,然後選擇適當大小的奇數--或者,有可能的話,使用素數的效果會更好一些--來作為雜湊表的初始值。
3、然後,向myMap中添加元素單元。
4、使用myMap進行資料對應、尋找、遍曆等操作。
5、調用myMap.RemoveAll()函數移除所有元素,釋放myMap佔用的記憶體空間。
CMap對應IMPLEMENT_SERIAL,從而支援使用者對其元素進行序列化(Serialization)以及傾注(Dumping)操作。在對
CMap的獨立元素進行傾注操作時,應該注意的是,你必須將傾注環境(Dump Context)的深度設定為1或者更大的數字。
經過上述討論,相信大家對Map及其在MFC中的實現都有了一定的瞭解,希望此文章能夠給大家帶來一點協助。也希望朋友們多聯絡、多指教,我的E-mail:islyb@163.com。謝謝!
map當然沒有順序遍曆的功能了,因為Map本身是用哈息表建立的,當然不能還原行順序遍曆的了
不過是可以遍曆的。
例如CMapPtrToPtr
POSITION pos = m_mapTest.GetStartPosition()
void* rKey = NULL;
void* rValue = NULL;
while (pos)
{
m_mapTest.GetNextAssoc(pos, rKey, rValue)
//do you want
}