Dalvik Virtual Machine Hash Table Implementation
The previous two Articles respectively introduced the interface and usage of hash tables in DVM. Next, let's take a look at how the hash table is implemented in DVM.
Data Structure
/*
* One entry in the hash table. "data" values are expected to be (or have
* The same characteristics as) Valid pointers. In particle, 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;
};
This structure represents a hash unit. It consists of two parts: a hash value of a four-byte space and a real data part with an indefinite length. When the unit is cleaned up, the memory occupied by the data part is cleared by the function call (hashfreefunc) (Entry-> data. This cleanup function is passed in when the hash table is created (the second parameter of dvmhashtablecreate ).
/*
* Expandable hash table.
*
* This structure shoshould 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;
};
This structure indicates a hash table. The member variables are
Tablesize: the size of the hash table, which must be a power of 2.
Numentries: Number of valid units used by the current hash table
Numdeadentries: Number of invalid cells in the hash table.
Pentries: pointer to the metadata of the hash form
Freefunc: Hash form metadata release function
Lock: the lock used to synchronously access the hash table.
As we will see later, in the implementation of a hash table, the hash value of an invalid unit is simply expressed as a constant without calling the release function:
# Define hash_tombstone (void *) 0 xcbcacccd) // invalid PTR Value
Create a hash table
/*
* 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;
}
First, Use assertions to check that the input initial size must be greater than 0. Then, use malloc to allocate the space of the most hash table in the local memory. Because local memory is allocated, the hash table will not be garbage collected, and free () needs to be called when not in use to explicitly release space, otherwise it will cause memory leakage. Next, initialize the access lock. Then, dexrounduppower2 is called to ensure that the initial size is aligned to the power of 2. Initialize the number of valid units and the number of invalid units to 0. Store the release function passed in the parameter in the freefunc variable. Call malloc to allocate space for the unit pointer. After the entire memset unit is complete, the system returns the structure pointer of the modified hash table.
Hash Table cleanup
/*
* 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 implementation. It traverses each unit. For an invalid unit, it simply sets the unit data zone to null. Call the release function for a valid unit and set the data zone to null.
Release a hash table
/*
* Free the table.
*/
Void dvmhashtablefree (hashtable * phashtable)
{
If (phashtable = NULL)
Return;
Dvmhashtableclear (phashtable );
Free (phashtable-> pentries );
Free (phashtable );
}
Call dvmhashtableclear to clean up the data with multiple units, free the memory allocated for the unit pointer in the hash table structure, and free the memory occupied by the hash table.
Hash Table search and Insertion Unit
In the hash table query, new units are inserted into the same function dvmhashtablelookup. It is distinguished by the last parameter doadd.
The first is the search implementation. it traverses from the first unit. If the unit meets the following conditions
"Pentry-> data! = Hash_tombstone & pentry-> hashvalue = itemhash & (* cmpfunc) (pentry-> data, item) = 0"
That is to say, the invalid unit must not be used, the hash value must be equal, and the result of calling the comparison function must be consistent (the returned value is 0 ).
If doadd is 1, the insert operation is performed. Before insertion, You need to determine whether the current space is sufficient to accommodate a new unit. If not, you need to expand the space.
Delete A hash table
Similar to searching, it is also used to find the first unit based on the hash value, and compare the value of the numeric pointer of the Unit with the value with the comparison. If they are consistent, the hash value of the unit is the constant hash_tombstone.