Hashhtml
Hash,通常翻譯作「散列」,也有直接音譯爲「哈希」的,就是把任意長度的輸入,經過散列算法,變換成固定長度的輸出,該輸出就是散列值。根據散列值做爲地址存放數據,這種轉換是一種壓縮映射,簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。查找關鍵字數據(如K)的時候,若結構中存在和關鍵字相等的記錄,則一定在f(K)的存儲位置上。由此,不需比較即可直接取得所查記錄。咱們稱這個對應關係f爲散列函數(Hash function),按這個事件創建的表爲散列表。java
綜上所述,根據散列函數f(key)和處理衝突的方法將一組關鍵字映象到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的「象」 做爲記錄在表中的存儲位置,這種表便稱爲散列表,這一映象過程稱爲散列造表或散列,所得的存儲位置稱散列地址。算法
Hash衝突 函數
對不一樣的關鍵字可能獲得同一散列地址,即key1≠key2,而f(key1)=f(key2),這種現象稱hash衝突。即:key1經過f(key1)獲得散列地址去存儲key1,同理,key2發現本身對應的散列地址已經被key1佔據了。測試
解決辦法(總共有四種):this
1.開放尋址法spa
所謂的開放定址法就是一旦發生了衝突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入 。.net
開放尋址法:Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)爲散列函數,m爲散列表長,di爲增量序列,可有下列三種取法:翻譯
1). di=1,2,3,…,m-1,稱線性探測再散列;3d
2). di=1^2,(-1)^2,2^2,(-2)^2,(3)^2,…,±(k)^2,(k<=m/2)稱二次探測再散列;
3). di=僞隨機數序列,稱僞隨機探測再散列。
用開放定址法解決衝突的作法是:當衝突發生時,使用某種探測技術(線性探測法、二次探測法(解決線性探測的堆積問題)、隨機探測法(和二次探測原理一致,不同的是:二次探測以定值跳躍,而隨機探測的散列地址跳躍長度是不定值))在散列表中造成一個探測序列。沿此序列逐個單元地查找,直到找到給定的關鍵字,或者碰到一個開放的地址(即該地址單元爲空)爲止插入便可。
好比說,咱們的關鍵字集合爲{12,67,56,16,25,37,22,29,15,47,48,34},表長爲12。 咱們用散列函數f(key) = key mod l2
當計算前S個數{12,67,56,16,25}時,都是沒有衝突的散列地址,直接存入:
計算key = 37時,發現f(37) = 1,此時就與25所在的位置衝突。
因而咱們應用上面的公式f(37) = (f(37)+1) mod 12 = 2。因而將37存入下標爲2的位置:
2.再哈希
再哈希法又叫雙哈希法,有多個不一樣的Hash函數,當發生衝突時,使用第二個,第三個,….,等哈希函數去計算地址,直到無衝突。雖然不易發生彙集,可是增長了計算時間。
3.鏈地址法(Java hashmap就是這麼作的)
鏈地址法的基本思想是:每一個哈希表節點都有一個next指針,多個哈希表節點能夠用next指針構成一個單向鏈表,將全部關鍵字爲同義詞的結點連接在同一個單鏈表中,如:
設有 m = 5 , H(K) = K mod 5 ,關鍵字值序例 5 , 21 , 17 , 9 , 15 , 36 , 41 , 24 ,按外鏈地址法所創建的哈希表以下圖所示:
4.創建一個公共溢出區
這種方法的基本思想是:將哈希表分爲基本表和溢出表兩部分,凡是和基本表發生衝突的元素,一概填入溢出表。
代碼實現:
Person類:
關於Java中的hashCode方法,詳情請見:
http://www.javashuo.com/article/p-wmfkfqqi-bs.html
1 package my.hash; 2 3 /** 4 * Person類 5 * 重寫hashCode方法和equals方法 6 * hashCode方法計算該對象的散列碼 7 * Java中每一個對象都有一個散列碼 8 * @author ASUS 9 * 10 */ 11 12 public class Person { 13 private String name; 14 private int age; 15 16 //set和get方法 17 public String getName() { 18 return name; 19 } 20 public void setName(String name) { 21 this.name = name; 22 } 23 public int getAge() { 24 return age; 25 } 26 public void setAge(int age) { 27 this.age = age; 28 } 29 30 //構造器 31 public Person(String name,int age){ 32 super(); 33 this.age=age; 34 this.name=name; 35 } 36 37 //輸出方法 38 public String toString() { 39 return "Person [name="+name+",age="+age+"]"; 40 } 41 42 //重寫hashcode方法 43 public int hashCode() { 44 final int prime=31; 45 int result=1; 46 result=prime*result+age; 47 result=prime*result+((name==null)?0:name.hashCode()); 48 return result; 49 } 50 51 /*重寫equals方法 52 * 重寫該方法時必須重寫hashCode方法,由於要確保一個對象映射到同一個存儲地址 53 */ 54 public boolean equals(Object object) { 55 if(this==object){ 56 return true; 57 } 58 if(object==null){ 59 return false; 60 } 61 if(getClass()!=object.getClass()){ 62 return false; 63 } 64 Person other=(Person)object; 65 if(age!=other.age){ 66 if(other.name!=null){ 67 return false; 68 } 69 }else if (!name.equals(other.name)) { 70 return false; 71 } 72 return true; 73 } 74 }
關於該Java代碼中的getClass方法,詳情請見:
https://blog.csdn.net/mark_to_win/article/details/38757259
測試類:
1 package my.hash; 2 3 import java.util.HashSet; 4 5 public class Test { 6 public static void main(String[] args) { 7 //構造6個person對象 8 Person p1=new Person("sam", 10); 9 Person p2=new Person("amy", 13); 10 Person p3=new Person("lili", 22); 11 Person p4=new Person("daming", 34); 12 Person p5=new Person("a", 2); 13 Person p6=new Person("b", 2); 14 15 //輸出a和b的hashcode值 16 System.out.println("a的hashcode值:"+p5.hashCode()+" b的hashcode值:"+p6.hashCode()); 17 18 //定義一個HashSet,將Person對象存儲在該集合中 19 HashSet<Person> set=new HashSet<Person>(); 20 21 //將Person對象添加進HashSet集合中 22 set.add(p6); 23 set.add(p5); 24 set.add(p4); 25 set.add(p3); 26 set.add(p2); 27 set.add(p1); 28 29 //遍歷HashSet,若p5和p6的hashCode一致,可是卻添加進了集合set,說明hashset底層解決了hash衝突的問題。 30 for(Person person :set){ 31 System.out.println(person); 32 } 33 } 34 }
運行結果: