hash函數進階二
1.理論篇
hash可以說是一種數據結構,類似於數組,鏈表;也可以說是一種算法過程,用於將數據散列,便於訪問。
如何使用好hash,主要體現在以下三點:
(1)生成hashcode的算法
(2)散列算法
(3)衝突處理策略
1.1.生成hashcode的算法
對於不同的對象,需要對應不同的關鍵字,因此算法需要儘量滿足不同對象的hashcode值不同。
譬如,現有最多僅有3個字符的全字母字符串待獲取hashcode,以下兩種算法效果就完全不同:
unsigned int gethashcode(const char *str)
{
unsigned int hashcode = 0;
while(*str)
{
hashcode = hashcode + *str++;
}
return (hashcode & 0x7FFFFFFF);
}
unsigned int gethashcode(const char *str)
{
unsigned int hashcode = 0;
while(*str)
{
hashcode = 24 * hashcode + *str++;
}
return (hashcode & 0x7FFFFFFF);
}
第一種生成的hashcode重複概率很大(如字符串"ab"和"ba"),因此不是一個好的算法。
1.2.散列算法
好的hashcode算法,會使得不同的對象有不同的hashcode,而散列就是通過hashcode,將對象存放在特定的位置;因此散列算法需要儘量使得不同的hashcode得到的位置不同,即衝突越少越好。
譬如,有如下兩種實現算法:
int getindex(int hashcode, int tablelength)
{
return (hashcode % tablelength);
}
int getindex(int hashcode, int a, int b)
{
return (a * hashcode + b);
}
第一種方法是除留餘數法:關鍵點在於除數的選取,一般選擇除數的原則是小於或等於表長的最大質數。
第二種方法是直接尋址法:簡單,不常用,可用於特定應用中,如輸入對象是1980到2000年段的大量數據,統計每個出生年有多少人。
- hashcode算法:hashcode = value - 1980
- 散列算法:index = hashcode
1.3.衝突解決策略
即使能夠保證hashcode值不同,對於大量的數據需要存放在長度遠小於數據量的情況,必然會出現散列衝突的情況,因此需要設計好的衝突解決方法,以提高效率。
常用的策略有:
- 開放定址法:衝突時,利用散列結果再散列,如:f(key) = (f(key) + di) % len;(di值可任選)
- 鏈地址法:衝突時,在衝突的位置利用鏈表鏈接起來。
bool putvalue(int hashcode, int value)
{
int index = 0, mark = 0;
index = getindex(hashcode, len);
while((array[index] != -1) && (mark < len))
{
index = getindex(index + 1, len);
mark++;
}
if(mark != len)
{
array[index] = value;
return true;
}
else
{
return false;
}
}
bool putvalue(int hashcode, int value)
{
int index = 0;
struct Node *pnode = NULL, *node = NULL;
index = getindex(hashcode, len);
pnode = array[index];
while(pnode->next != NULL)
{
pnode = pnode->next;
}
/*malloc node and set value*/
/*.......*/
pnode->next = node;
return true;
}
2.實例篇
我們以JDK和C++STL中的hash實現爲例子,分析hash的實現過程。
2.1JDK中的hashtable應用
(1)存儲目標對象數組及初始化
/**
* The hash table data.
*/
private transient Entry<?,?>[] table;
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
thresdhold是一個門限值,當達到該值後,在加入對象會加大長度,重做table。
(2)對象及hashcode算法
對象:<k key, V value>;使用key作爲關鍵字,通過hashcode算法散列到table,而後將<key, value>對象存儲。
默認的hashcode算法是native類型,無法查看到源代碼,但可以參考string對象的覆寫實現:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
注:因對於相同值得string對象,其hashcode需要一樣,因此藉助輔助變量hash來判斷是否通過string(str)繼承而來。
(3)添加:散列算法、衝突策略
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
散列算法:採用的是除留餘數法。
衝突策略:鏈接法。
(4)查詢、獲取
public synchronized boolean containsKey(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
2.2 C++ STL中的hashtable應用
未完待續。。。