您好,登錄后才能下訂單哦!
散列算法與散列碼的區別有哪些?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
一、引入
/** * Description:新建一個類作為map的key */ public class Groundhog { protected int number; public Groundhog(){ } public Groundhog(int number) { this.number = number; } @Override public String toString() { return "Groundhog{" + "number=" + number + '}'; } } /** * Description:新建一個類作為map的value */ public class Prediction { private boolean shadow=Math.random() > 0.5; @Override public String toString() { if (shadow) return "Six more weeks of Winter"; else return "Early Spring!"; } } /** * Description:測試類 */ public class SpringDetector { public static void detectSpring(Class grondHotClass) throws Exception{ Constructor constructor = grondHotClass.getConstructor(new Class[]{int.class}); Map map=new HashMap(); for (int i=0;i<10;i++){ map.put(constructor.newInstance(new Object[]{new Integer(i)}),new Prediction()); } System.out.println("map="+map); Groundhog groundhog=(Groundhog)constructor.newInstance(new Object[]{new Integer(3)}); System.out.println(groundhog); if (map.containsKey(groundhog)) {//查找這個key是否存在 System.out.println((Prediction)map.get(groundhog)); }else { System.out.println("key not find:"+groundhog); } } public static void main(String[] args) { try { detectSpring(Groundhog.class); } catch (Exception e) { e.printStackTrace(); } } }
看這個結果,問題就來了,map中明明存在Groudhog{number=3},為什么結果顯示的是Key not find呢??問題出在哪里呢?原來是Groudhog類沒有重寫hashCode()方法,所以這里是使用Object的hashCode()方法生成散列碼,而他默認是使用對象的地址計算散列碼。因此,由Groudhog(3)生成的第一個實例的散列碼與Groudhog(3)生成的散列碼是不同的,所以無法查找到 key。但是僅僅重寫hashCode()還是不夠的,除非你重寫equals()方法。原因在于不同的對象可能計算出同樣的hashCode的值,hashCode 的值并不是唯一的,當hashCode的值一樣時,就會使用equals()判斷當前的“鍵”是否與表中的存在的鍵“相同”,即“
二、理解hashCode()
散列的價值在于速度:散列使得查詢得以快速執行。由于速度的瓶頸是對“鍵”進行查詢,而存儲一組元素最快的數據結構是數組,所以用它來代表鍵的信息,注意:數組并不保存“鍵”的本身。而通過“鍵”對象生成一個數字,將其作為數組的下標索引。這個數字就是散列碼,由定義在Object的hashCode()生成(或成為散列函數)。同時,為了解決數組容量被固定的問題,不同的“鍵”可以產生相同的下標。那對于數組來說?怎么在同一個下標索引保存多個值呢??原來數組并不直接保存“值”,而是保存“值”的 List。然后對 List中的“值”使用equals()方法進行線性的查詢。這部分的查詢自然會比較慢,但是如果有好的散列函數,每個下標索引只保存少量的值,只對很少的元素進行比較,就會快的多。
不知道大家有沒有理解我上面在說什么。不過沒關系,下面會有一個例子幫助大家理解。不過我之前一直被一個問題糾結:為什么一個hashCode的下標存的會有多個值?因為hashMap里面只能有唯一的key啊,所以只能有唯一的value在那個下標才對啊。這里就要提出一個新的概念哈希沖突的問題,借用網上的一個例子:
比如:數組的長度是5。這時有一個數據是6。那么如何把這個6存放到長度只有5的數組中呢。按照取模法,計算6%5,結果是1,那么就把6放到數組下標是1的位置。那么,7就應該放到2這個位置。到此位置,哈希沖突還沒有出現。這時,有個數據是11,按照取模法,11%5=1,也等于1。那么原來數組下標是1的地方已經有數了,是6。這時又計算出1這個位置,那么數組1這個位置,就必須儲存兩個數了。這時,就叫哈希沖突。沖突之后就要按照順序來存放了。所以這里Java中用的解決方法就是在這個hashCode上存一個List,當遇到相同的hashCode時,就往這個List里add元素就可以了。這才是hash原理的精髓所在啊!哈哈、糾結我一天。
三、HashMap的性能因子
容量(Capacity):散列表中的數量。
初始化容量(Initial capacity):創建散列表時桶的數量。HashMap 和 HashSet都允許你在構造器中制定初始化容量。
尺寸(Size):當前散列表中記錄的數量。
負載因子(Load factor):等于"size/capacity"。負載因子為0,表示空的散列表,0.5表示半滿的散列表,依次類推。輕負載的散列表具有沖突少、適宜插入與適宜查詢的特點(但是使用迭代器遍歷會變慢)。HashMap和hashSet的構造器允許你制定負載因子。這意味著,當負載達到制定值時,容器會自動成倍的增加容量,并將原有的對象重新分配,存入新的容器內(這稱為“重散列”rehashing)。HashMap默認的負載因子為0.75,這很好的權衡了時間和空間的成本。
備注:為使散列分布均衡,Java的散列函數都使用2的整數次方來作為散列表的理想容量。對現代的處理器來說,除法和求余是最慢的動作。使用2的整數次方的散列表,可用掩碼代替除法。因為get()是使用最多的操作,求余數的%操作是其開銷的大部分,而使用2的整數次方可以消除此開銷(也可能對hashCode()有些影響)
四、怎么重寫hashCode()
現在的IDE工具中,一般都能自動的幫我們重寫了hashCode()和equals()方法,但那或許并不是最優的,重寫hashCode()有兩個原則:
必須速度快,并且必須有意義。也就是說,它必須基于對象的內容生成散列碼。
應該產生分布均勻的散列碼。如果散列碼都集中在一塊,那么在某些區域的負載就會變得很重。
下面是怎么寫出一份像樣的hashCode()的基本指導:
1、給int變量result 賦予某個非零值常量,例如 17。
2、為每個對象內每個有意義的屬性f (即每個可以做equals()的屬性)計算出一個 int 散列碼c:
3、合并計算得到的散列值:result=37*result+c;
4、返回 result;
5、檢查hashCode()最后生成的結果,確保相同的對象有相同的散列碼。
五、自定義HashMap
下面我們將自己寫一個hashMap,便于了解底層的原理,大家如果看的懂下面的代碼,也就很好的理解了hashCode的原理了。
/** * Description:首先新建一個類作為map中存儲的對象并重寫了hashCode()和equals()方法 */ public class MPair implements Map.Entry,Comparable { private Object key,value; public MPair(Object key,Object value) { this.key = key; this.value=value; } @Override public int compareTo(Object o) { return ((Comparable)key).compareTo(((MPair)o).key); } @Override public Object getKey() { return key; } @Override public Object getValue() { return value; } @Override public int hashCode() { int result = key != null ? key.hashCode() : 0; result = 31 * result + (value != null ? value.hashCode() : 0); return result; } @Override public boolean equals(Object o) { return key.equals(((MPair)o).key); } @Override public Object setValue(Object v) { Object result=value; this.value=v; return result; } @Override public String toString() { return "MPair{" + "key=" + key + ", value=" + value + '}'; }
public class SimpleHashMap extends AbstractMap { private static final int SZ=3;//定一個初始大小的哈希表容量 private LinkedList[] linkedLists=new LinkedList[SZ];//建一個hash數組,用linkedList實現 public Object put(Object key,Object value){ Object result=null; int index=key.hashCode() % SZ;//對key的值做求模法求出index if (index<0) index=-index; if (linkedLists[index]==null) linkedLists[index]=new LinkedList();//如果這個index位置沒有對象,就新建一個 LinkedList linkedList = linkedLists[index];//取出這個index的對象linkedList MPair mPair = new MPair(key,value);//新建要存儲的對象mPair ListIterator listIterator = linkedList.listIterator(); boolean found =false; while (listIterator.hasNext()){//遍歷這個index位置的List,如果查找到跟之前一樣的對象(根據equals來比較),則更新那個key對應的value Object next = listIterator.next(); if (next.equals(mPair)){ result = ((MPair) next).getValue(); listIterator.set(mPair);//更新動作 found=true; break; } } if (!found) linkedLists[index].add(mPair);//如果沒有找到這個對象,則在這index的List對象上新增一個元素。 return result; } public Object get(Object key){ int index = key.hashCode() % SZ; if (index<0) index=-index; if (linkedLists[index]==null) return null; LinkedList linkedList = linkedLists[index]; MPair mPair=new MPair(key,null);//新建一個空的對象值,因為equals()的比較是看他們的key是否相等,而在List中的遍歷對象的時候,是通過key來查找對象的。 ListIterator listIterator = linkedList.listIterator(); while (listIterator.hasNext()){ Object next = listIterator.next(); if (next.equals(mPair)) return ((MPair)next).getValue();//找到了這個key就返回這個value } return null; } @Override public Set<Entry> entrySet() { Set set=new HashSet(); for (int i=0;i<linkedLists.length;i++){ if (linkedLists[i]==null) continue; Iterator iterator = linkedLists[i].iterator(); while (iterator.hasNext()){ set.add(iterator.next()); } } return set; } public static void main(String[] args) { SimpleHashMap simpleHashMap=new SimpleHashMap(); simpleHashMap.put("1", "1"); simpleHashMap.put("2", "2"); simpleHashMap.put("3","3"); simpleHashMap.put("4","4");//這里有四個元素,其中key是1和key是4的index是一樣的,所以index為1的List上面存了兩個元素。 System.out.println(simpleHashMap); Object o = simpleHashMap.get("1"); System.out.println(o); Object o1 = simpleHashMap.get("4"); System.out.println(o1); } }
看完上述內容,你們掌握散列算法與散列碼的區別有哪些的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。