http://hi.baidu.com/wei83523408/blog/item/878ebd3b8898d5e115cecb2b.html
类名 | 关键字类型 | 元素值类型 |
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 |
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; } |
四、 使用MFC中的CMap类
有关MFC中的CMap类的概况,上面的文字段落中已经陆续提及,在此不再赘言。下面,列出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 |