Java中的HashMap使用散列來高效的查找和存儲值。HashMap內部使用Map.Entry
的形式來保存key和value,使用put(key,value)
方法存儲值,使用get(key)
方法查找值。編程
Java中的hashCode()
方法,是頂層對象Object
中的方法,所以Java中全部的對象都會帶有hashCode()
方法。
在各類最佳實踐中,都會建議在編寫本身的類的時候要同時覆蓋hashCode()
和equals()
方法,可是在使用散列的數據結構時(HashMap
,HashSet
,LinkedHashSet
,LinkedHashMap
),
若是不爲鍵覆蓋hashCode()
和equals()
方法,將沒法正確的處理該鍵。api
hashCode()
方法返回一個int值,這個int值就是用這個對象的hashCode()
方法產生的hash值。數組
在散列表中查找一個值的過程爲,先經過鍵的hashCode()
方法計算hash值,而後使用hash值產生下標並使用下標查找數組,這裏爲何要用數組呢,由於數組是存儲一組元素最快的數據結構,所以使用數組來表示鍵的信息。數據結構
因爲數組的容量(也就是表中的桶位數)是固定的,因此不一樣的鍵能夠產生相同的下標,也就是說,可能會有衝突,所以數組多大就不重要了,任何鍵總能在數組中找到它的位置。ide
數組並不直接保存值,由於不一樣的鍵可能產生相同的數組下標,數組保存的是LinkedList,所以,散列表的存儲結構外層是一個數組,容量固定,數組的每一項都是保存着Entry Object
(同時保存key和value)的LinkedList。函數
因爲下標的衝突,不一樣的鍵可能會產生相同的bucket location
,在使用put(key,value)
時,若是兩個鍵產生了相同的bucket location
,因爲LinkedList的長度是可變的,因此會在該LinkedList中再增長一項Entry Object
,其中保存着key和value。code
鍵使用hashCode()
方法產生hash值後,利用hash值產生數組的下標,找到值在散列表中的桶位(bucket),也就是在哪個LinkedList中,若是該桶位只有一個的Object,則返回該Value,若是該桶位有多個Object,那麼再對該LinkedList中的Entry Object
的鍵使用equals()
方法進行線性的查詢,最後找到該鍵的值並返回。對象
最後對LinkedList進行線性查詢的部分會比較慢,可是,若是散列函數好的話,數組的每一個位置就只有較少的值,所以不是查詢整個LinkedList,而是快速地跳到數組的某個位置,只對不多的元素進行比較,這就是HashMap
會如此快的緣由。遞歸
在知道了散列的原理後咱們能夠本身實現一個簡單的HashMap
(例子來源於《Java編程思想(第四版)》)get
public class SimpleHashMap<K, V> extends AbstractMap<K, V> { //內部數組的容量 static final int SIZE = 997; //buckets數組,內部是一個鏈表,鏈表的每一項是Map.Entry形式,保存着HashMap的值 @SuppressWarnings("unchecked") LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE]; public V put(K key, V value) { V oldValue = null; //使用hashCode()方法產生hash值,使用hash值與數組容量取餘得到數組的下標 int index = Math.abs(key.hashCode()) % SIZE; //若是該桶位爲null,則插入一個鏈表 if (buckets[index] == null) { buckets[index] = new LinkedList<>(); } //得到bucket LinkedList<MapEntry<K, V>> bucket = buckets[index]; MapEntry<K, V> pair = new MapEntry<>(key, value); boolean found = false; ListIterator<MapEntry<K, V>> it = bucket.listIterator(); while (it.hasNext()) { MapEntry<K, V> iPair = it.next(); //對鍵使用equals()方法線性查詢value if (iPair.getKey().equals(key)) { oldValue = iPair.getValue(); //找到了鍵之後更改鍵原來的value it.set(pair); found = true; break; } } //若是沒找到鍵,在bucket中增長一個Entry if (!found) { buckets[index].add(pair); } return oldValue; } //get()與put()的工做方式相似 @Override public V get(Object key) { //使用hashCode()方法產生hash值,使用hash值與數組容量取餘得到數組的下標 int index = Math.abs(key.hashCode()) % SIZE; if (buckets[index] == null) { return null; } //使用equals()方法線性查找鍵 for (MapEntry<K, V> iPair : buckets[index]) { if (iPair.getKey().equals(key)) { return iPair.getValue(); } } return null; } @Override public Set<Map.Entry<K, V>> entrySet() { Set<Map.Entry<K, V>> set = new HashSet<>(); for (LinkedList<MapEntry<K, V>> bucket : buckets) { if (bucket == null) { continue; } for (MapEntry<K, V> mpair : bucket) { set.add(mpair); } } return set; } public static void main(String[] args) { SimpleHashMap<String, String> m = new SimpleHashMap<>(); m.putAll(Countries.capitals(25)); System.out.println(m); System.out.println(m.get("ERITREA")); System.out.println(m.entrySet()); } }
若是hashCode()
產生的hash值可以讓HashMap中的元素均勻分佈在數組中,能夠提升HashMap的運行效率。
一個良好的hashCode()
方法首先是能快速地生成hash值,而後生成的hash值能使HashMap中的元素在數組中儘可能均勻的分佈,
hash值不必定是惟一的,由於容量是固定的,總會有下標衝突的狀況產生。
《Effective Java》中給出了覆蓋hashCode()
方法的最佳實踐:
把某個非零的常數值,好比17,保存在一個名爲result的int類型中。
對於對象中的每一個關鍵域f(指equals()
方法中涉及的域),完成如下步驟:
爲該域計算int類型的散列碼c,根據域的類型的不一樣,又能夠分爲如下幾種狀況:
若是該域是boolean類型,則計算(f?1:0)
若是該域是String類型,則使用該域的hashCode()
方法
若是該域是byte、char、short或int類型,則計算(int)f
若是該域是long類型,則計算(int)(f^>>>32)
若是該域是float類型,則計算Float.floatToIntBits(f)
若是該域是double類型,則計算Double.doubleToLongBits(f)
返回一個long類型的值,再根據long類型的域,生成int類型的散列碼
若是該域是一個對象引用,而且該類的equals()
方法經過遞歸調用equals方式來比較這個域,則一樣爲這個域遞歸地調用hashCode()
若是該域是一個數組,則要把每個元素看成單獨的域來處理,也就是說遞歸地應用上述原則
按照公式:result = 31 * result + c,返回result。
寫一個簡單的類並用上述的規則來覆蓋hashCode()
方法
public class SimpleHashCode { private static long counter = 0; private final long id = counter++; private String name; @Override public int hashCode(){ int result = 17; if (name != null){ result = 31 * result + name.hashCode(); } result = result * 31 + (int) id; return result; } @Override public boolean equals(Object o){ return o instanceof SimpleHashCode && id == ((SimpleHashCode)o).id; } }