似乎是最實用的hashtable知識總結
哈希表:將對象轉換為索引,然後存儲在數組中。
定義注意點:
- 對象:就是面向對象中的對象,可以為任何東西。整數、浮點數、日期、字元串、類。
- 轉換:通過
hash函數
來完成,hash函數
是hash表的核心與難點。對於整數,可以將取模運算
作為hash函數
。 - 數組:hash表本質是就是一個數組(
靜態、動態
),這也是名稱中”表”的含義。
體現的電腦思想: 空間換時間
思考角度,當空間無限時,可以使用O(1)完成各項操作,當空間只要1個時,就退化為線性表O(n)。
哈希表關注的核心問題
- 哈希函數如何設計
- 如何解決hash衝突
對於不同的關鍵字得到了同一個hash地址,這種現象稱為hash衝突(collision),形式化為:
key1≠key2,f(key1)==f(key2)
,其中f
為hash函數。
hash函數的設計原則
-
一致性:如果
a==b
,則hash(a)==hash(b)
,這是java自定義類時必須需重寫的hashcode方法
原因。 -
高效性:計算高效便捷,O(1),這也是使用動態數組,在適當的情況下resize的原因。
-
均勻性:哈希值的分布越均勻越好,這就是取模法中模為質數的原因。
整數轉換為索引的方法:取模法
hashcode=val%M
,其中M為一個質數,M的參考取值請點擊這兒。注意,公式總val
為正整數,如果類型為int
,可以先進行去除符號操作:val=val&ox7fffffff
。因為從二進位的角度看ox7fffffff
就是0和31個1,正好把符號位過濾掉。
任何對象都可以表示為整數。
- 浮點數:在電腦內部都是用32位或者64位二進位表示,從整數的角度去解析這些位,就找到了浮點數對應的整數。
- 字元串:字元串本質上可以理解為B(base)進位數,其中B可以是不同字元串的個數。例如26。也可以是任意設定的一個質數。
- 例如:
code=c*26^3+o*26^2+d*26^1+e*26^0
- 例如:
abcd=a*B^3+b*B^2+c*B^1+d*B^0
,
- 例如:
進位表示的形式簡化以及編程實現:
hash(code)=(
c*B^3+o*B^2+d*B^1+e*B^0
)%M,可以表示為每一位乘以base,在加下一位=
((((c*B+o)*B+d)*B+e)%M
,很重要,在java字元串的hashcode方法中B=31=
((((c%M)*B+o)%M*B+d)%M*B+e)%M
,取余操作可以拿到括弧裡面去。(此性質快速冪演算法中很常用)
int hash=0;
for(int i=0;i<s.length;i++){
hash=(hash*B+s.charAt(i))%M;
}
//java中B的是31,不在乎是否溢出,只要返回的是一個整數就OK,不知道M是什麼,所以就沒有出現M。
-
日期類型:考慮每個部分,每部分表示不同的權重(進位思維)。
- Date: year,month,day,則
hash(date)=(((date.year%M)*B+date.month%M)*B+date.day)%M
- Date: year,month,day,則
-
類:分別將類的每一個欄位當做B進位中的某一位。依據B進位數進行轉換。
當將自定義的類作為hashmap和hashSet的Key時,必須重寫hashcode方法和equal方法。
1.因為默認的hashcode()方法取對象的地址為基礎獲得的,而new()同一類的不同實例對象地址不同,使得hashcode的結果也不同,這就不滿足一致性,例如,new Person(“小明”)兩次,它們的hashcode不同,但這顯然就不合理。
2.重寫hashcode()只是為了獲得正確的hash值,但當衝突了,還需要逐個欄位進行比較才能確定是否相等,這就要求重寫equal來完成,因為默認的equal就等於==
,含義為比較對象地址。
自定義hashcode和equals的實例
基本思路利用已有基本類型的包裝類和String類的hashcode()
方法來生成我們的hashcode()
public class Student {
Integer grade;
Integer cls;
String name;
//省去無關程式碼
@Override
public int hashCode() {//套路:模仿String,Base取31
int B=31;
int hash=0;
hash=hash*B+grade.hashCode();
hash=hash*B+cls.hashCode();
hash=hash*B+name.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {//有套路,逐個欄位比較
if(this==obj) return true;
if(obj==null)return false;
if(this.getClass()!=obj.getClass()) return false;
Student another=(Student) obj;
return this.grade.equals(another.grade) &&
this.cls.equals(another.cls) &&
this.name.equals(another.name);//字元串比較相等,equals
}
}
完整程式碼以及測試用例,請點擊這兒。
hash衝突的解決方法:鏈地址法
數組中每個元素保留的是地址。數組中每個元素的位置是N%M
去掉符號位:hashcode(k1)&0X7FFFFFFF
動態空間(擴容和縮容)處理N/M>=upperTol
和N/M<lowerTol
實現自己的hashtable,採用TreeMap作為鏈接衝突元素的容器
都是先獲得key索引,然後再獲某個元素。TreeMap<K,V> map=hashtable[hash(key)]
。
完整源碼及測試程式碼請點擊這兒。
更多關於hash衝突的辦法
- 開放地址法。
- 再哈希法:rehashing.