Dalvik虚拟机 之 哈希表实现篇
前面两篇分别介绍了dvm里哈希表的接口和使用。下面我们来看看dvm里哈希表是怎么实现的。
数据结构
/*
* One entry in the hash table. "data" values are expected to be (or have
* the same characteristics as) valid pointers. In particular, a NULL
* value for "data" indicates an empty slot, and HASH_TOMBSTONE indicates
* a no-longer-used slot that must be stepped over during probing.
*
* Attempting to add a NULL or tombstone value is an error.
*
* When an entry is released, we will call (HashFreeFunc)(entry->data).
*/
struct HashEntry {
u4 hashValue;
void* data;
};
该结构表示一个哈希单元。它由两部分,一个四字节空间的哈希值和一个不定长的真正的数据部分。在该单元被清理时,该数据部分所占据的内存会被函数调用(HashFreeFunc)(entry->data)清理掉。该清理函数是在哈希表创建时传入的(dvmHashTableCreate()的第二个参数)。
/*
* Expandable hash table.
*
* This structure should be considered opaque.
*/
struct HashTable {
int tableSize; /* must be power of 2 */
int numEntries; /* current #of "live" entries */
int numDeadEntries; /* current #of tombstone entries */
HashEntry* pEntries; /* array on heap */
HashFreeFunc freeFunc;
pthread_mutex_t lock;
};
该结构表示一个哈希表。其成员变量分别是
tableSize: 哈希表的大小,必须是2的幂次。
numEntries: 当前哈希表用于的有效单元数量
numDeadEntries: 改哈希表拥有的无效单元数量。
pEntries: 表示指向该哈希表单元数据的指针
freeFunc: 哈希表单元释放函数
lock:用于同步访问该哈希表的锁。
我们后面会看到,哈希表实现中对于无效单元只是简单的将其哈希值表示为一个常量,而没有调用释放函数:
#define HASH_TOMBSTONE ((void*) 0xcbcacccd) // invalid ptr value
哈希表创建
/*
* Create and initialize a hash table.
*/
HashTable* dvmHashTableCreate(size_t initialSize, HashFreeFunc freeFunc)
{
HashTable* pHashTable;
assert(initialSize > 0);
pHashTable = (HashTable*) malloc(sizeof(*pHashTable));
if (pHashTable == NULL)
return NULL;
dvmInitMutex(&pHashTable->lock);
pHashTable->tableSize = dexRoundUpPower2(initialSize);
pHashTable->numEntries = pHashTable->numDeadEntries = 0;
pHashTable->freeFunc = freeFunc;
pHashTable->pEntries =
(HashEntry*) malloc(pHashTable->tableSize * sizeof(HashEntry));
if (pHashTable->pEntries == NULL) {
free(pHashTable);
return NULL;
}
memset(pHashTable->pEntries, 0, pHashTable->tableSize * sizeof(HashEntry));
return pHashTable;
}
首先用断言检查传入的初始大小必须大于0。然后用malloc分配本地内存最为哈希表的空间。由于分配的是本地内存,该哈希表不会被垃圾收集,并且需要在不使用时调用free()来显式的释放空间,否则将引起内存泄露。接下来初始化访问锁。随后调用dexRoundUpPower2来保证初始大小为2幂次方对齐。将有效单元数量和无效单元数量初始化为0。将参数传入的释放函数存放在freeFunc变量里。接着调用malloc为单元指针分配空间。 最后memset整个单元后返回改哈希表结构指针。
哈希表清理
/*
* Clear out all entries.
*/
void dvmHashTableClear(HashTable* pHashTable)
{
HashEntry* pEnt;
int i;
pEnt = pHashTable->pEntries;
for (i = 0; i < pHashTable->tableSize; i++, pEnt++) {
if (pEnt->data == HASH_TOMBSTONE) {
// nuke entry
pEnt->data = NULL;
} else if (pEnt->data != NULL) {
// call free func then nuke entry
if (pHashTable->freeFunc != NULL)
(*pHashTable->freeFunc)(pEnt->data);
pEnt->data = NULL;
}
}
pHashTable->numEntries = 0;
pHashTable->numDeadEntries = 0;
}
dvmHashTableClear实现。它遍历每个单元,对于无效的单元,简单的将该单元数据区置为NULL。对有效单元调用释放函数后将数据区置为NULL。
哈希表释放
/*
* Free the table.
*/
void dvmHashTableFree(HashTable* pHashTable)
{
if (pHashTable == NULL)
return;
dvmHashTableClear(pHashTable);
free(pHashTable->pEntries);
free(pHashTable);
}
首先调用dvmHashTableClear清理多有单元数据,然后free掉哈希表结构中为单元指针分配的内存,最后free掉该哈希表本身占据的内存。
哈希表查找和插入单元
哈希表查找,插入新单元是被放到同一个函数dvmHashTableLookup里实现的。用最后一个参数doAdd来区分开来。
首次是查找的实现,它从第一个单元开始遍历,如果该单元满足下面的条件
“pEntry->data != HASH_TOMBSTONE && pEntry->hashValue == itemHash && (*cmpFunc)(pEntry->data, item) == 0”
也就是说必须 不能使无效单元,而且哈希值相等,并且调用比较函数的结果相符(返回值为0)。
如果doAdd为1,则进行插入操作。再插入之前,需要判断当前表示空间是否足够容纳新插入一个单元,不够的话需要扩展空间。
哈希表删除
和查找类似,也是根据哈希值找到第一个单元后,比较该单元的数值指针的值和带比较的是否一致。一致的话就表示该单元哈希值为常量HASH_TOMBSTONE。