摘要:中的使用散列來高效的查找和存儲值。理解中的方法是頂層對象中的方法因此中所有的對象都會帶有方法。中給出了覆蓋方法的最佳實踐把某個非零的常數值比如保存在一個名為的類型中。
Java中的HashMap使用散列來高效的查找和存儲值。HashMap內部使用Map.Entry的形式來保存key和value,使用put(key,value)方法存儲值,使用get(key)方法查找值。
理解hashCode()Java中的hashCode()方法,是頂層對象Object中的方法,因此Java中所有的對象都會帶有hashCode()方法。
在各種最佳實踐中,都會建議在編寫自己的類的時候要同時覆蓋hashCode()和equals()方法,但是在使用散列的數據結構時(HashMap,HashSet,LinkedHashSet,LinkedHashMap),
如果不為鍵覆蓋hashCode()和equals()方法,將無法正確的處理該鍵。
hashCode()方法返回一個int值,這個int值就是用這個對象的hashCode()方法產生的hash值。
HashMap的工作原理在散列表中查找一個值的過程為,先通過鍵的hashCode()方法計算hash值,然后使用hash值產生下標并使用下標查找數組,這里為什么要用數組呢,因為數組是存儲一組元素最快的數據結構,因此使用數組來表示鍵的信息。
由于數組的容量(也就是表中的桶位數)是固定的,所以不同的鍵可以產生相同的下標,也就是說,可能會有沖突,因此數組多大就不重要了,任何鍵總能在數組中找到它的位置。
數組并不直接保存值,因為不同的鍵可能產生相同的數組下標,數組保存的是LinkedList,因此,散列表的存儲結構外層是一個數組,容量固定,數組的每一項都是保存著Entry Object(同時保存key和value)的LinkedList。
由于下標的沖突,不同的鍵可能會產生相同的bucket location,在使用put(key,value)時,如果兩個鍵產生了相同的bucket location,由于LinkedList的長度是可變的,所以會在該LinkedList中再增加一項Entry Object,其中保存著key和value。
鍵使用hashCode()方法產生hash值后,利用hash值產生數組的下標,找到值在散列表中的桶位(bucket),也就是在哪一個LinkedList中,如果該桶位只有一個的Object,則返回該Value,如果該桶位有多個Object,那么再對該LinkedList中的Entry Object的鍵使用equals()方法進行線性的查詢,最后找到該鍵的值并返回。
最后對LinkedList進行線性查詢的部分會比較慢,但是,如果散列函數好的話,數組的每個位置就只有較少的值,因此不是查詢整個LinkedList,而是快速地跳到數組的某個位置,只對很少的元素進行比較,這就是HashMap會如此快的原因。
在知道了散列的原理后我們可以自己實現一個簡單的HashMap(例子來源于《Java編程思想(第四版)》)
public class SimpleHashMap編寫良好的hashCode()方法extends AbstractMap { //內部數組的容量 static final int SIZE = 997; //buckets數組,內部是一個鏈表,鏈表的每一項是Map.Entry形式,保存著HashMap的值 @SuppressWarnings("unchecked") LinkedList >[] 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 > bucket = buckets[index]; MapEntry pair = new MapEntry<>(key, value); boolean found = false; ListIterator > it = bucket.listIterator(); while (it.hasNext()) { MapEntry 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 iPair : buckets[index]) { if (iPair.getKey().equals(key)) { return iPair.getValue(); } } return null; } @Override public Set > entrySet() { Set > set = new HashSet<>(); for (LinkedList > bucket : buckets) { if (bucket == null) { continue; } for (MapEntry mpair : bucket) { set.add(mpair); } } return set; } public static void main(String[] args) { SimpleHashMap 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; } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65980.html
摘要:的工作原理是近年來常見的面試題。讓我們再來看看這些問題設計哪些知識點的概念中解決碰撞的方法和的應用,以及它們在中的重要性不可變對象的好處多線程的條件競爭重新調整的大小總結的工作原理基于原理,我們通過和方法儲存和獲取對象。 HashMap 的工作原理是近年來常見的 Java 面試題。幾乎每個 Java 程序員都知道 HashMap,都知道哪里要用 HashMap,知道Hashtable和...
摘要:基礎知識復習后端掘金的作用表示靜態修飾符,使用修飾的變量,在中分配內存后一直存在,直到程序退出才釋放空間。將對象編碼為字節流稱之為序列化,反之將字節流重建成對象稱之為反序列化。 Java 學習過程|完整思維導圖 - 后端 - 掘金JVM 1. 內存模型( 內存分為幾部分? 堆溢出、棧溢出原因及實例?線上如何排查?) 2. 類加載機制 3. 垃圾回收 Java基礎 什么是接口?什么是抽象...
摘要:原文地址游客前言金三銀四,很多同學心里大概都準備著年后找工作或者跳槽。最近有很多同學都在交流群里求大廠面試題。 最近整理了一波面試題,包括安卓JAVA方面的,目前大廠還是以安卓源碼,算法,以及數據結構為主,有一些中小型公司也會問到混合開發的知識,至于我為什么傾向于混合開發,我的一句話就是走上編程之路,將來你要學不僅僅是這些,豐富自己方能與世接軌,做好全棧的裝備。 原文地址:游客kutd...
閱讀 743·2021-10-09 09:44
閱讀 2016·2021-09-22 15:54
閱讀 5057·2021-09-22 10:55
閱讀 1442·2019-08-29 18:41
閱讀 777·2019-08-29 11:24
閱讀 2104·2019-08-28 18:20
閱讀 1030·2019-08-26 11:51
閱讀 3049·2019-08-26 11:00