HashMap/HashSet,hashCode,哈希表

 hash code、equals和「==」三者的關係java

 1) 對象相等則hashCode必定相等;
  2) hashCode相等對象未必相等。c++

== 是比較地址是否相等,JAVA中聲明變量都是引用嘛,不一樣的引用,可能指向同一個地址。程序員

equals 是比較值是否相等。面試

 

1.若是是基本變量,沒有hashcode和equals方法,基本變量的比較方式就只有==,;

2.若是是變量,因爲在java中全部變量定義都是一個指向實際存儲的一個句柄(你能夠理解爲c++中的指針),在這裏==是比較句柄的地址(你能夠理解爲指針的存儲地址),而不是句柄指向的實際內存中的內容,若是要比較實際內存中的內容,那就要用equals方法,可是!!!

若是是你本身定義的一個類,比較自定義類用equals和==是同樣的,都是比較句柄地址,由於自定義的類是繼承於object,而object中的equals就是用==來實現的,你能夠看源碼。

那爲何咱們用的String等等類型equals是比較實際內容呢,是由於String等經常使用類已經重寫了object中的equals方法,讓equals來比較實際內容,你也能夠看源碼。

3. hashcode
在通常的應用中你不須要了解hashcode的用法,但當你用到hashmap,hashset等集合類時要注意下hashcode。

你想經過一個object的key來拿hashmap的value,hashmap的工做方法是,經過你傳入的object的hashcode在內存中找地址,當找到這個地址後再經過equals方法來比較這個地址中的內容是否和你原來放進去的同樣,同樣就取出value。

因此這裏要匹配2部分,hashcode和equals
但假如說你new一個object做爲key去拿value是永遠得不到結果的,由於每次new一個object,這個object的hashcode是永遠不一樣的,因此咱們要重寫hashcode,你能夠令你的hashcode是object中的一個恆量,這樣永遠能夠經過你的object的hashcode來找到key的地址,而後你要重寫你的equals方法,使內存中的內容也相等。。。算法

 

 

首先,從語法角度,也就是從強制性的角度來講,hashCode和equals是兩個獨立的,互不隸屬,互不依賴的方法,equals成立與hashCode相等這兩個命題之間,誰也不是誰的充分條件或者必要條件。  
   
  可是,從爲了讓咱們的程序正常運行的角度,咱們應當向Effective   Java中所言  
   
  重載equals的時候,必定要(正確)重載hashCode  
   
  使得equals成立的時候,hashCode相等,也就是a.equals(b)->a.hashCode()   ==   b.hashCode(),或者說此時,equals是hashCode相等的充分條件,hashCode相等是equals的必要條件(從數學課上咱們知道它的逆否命題:hashCode不相等也不會equals),可是它的逆命題,hashCode相等必定equals以及否命題不equals時hashCode不等都不成立。  
   
  因此,若是面試的時候,最好把hashCode與equals之間沒有強制關係,以及根據(沒有語法約束力的)規範的角度,應當作到...這兩層意思都說出來:P  編程

  總結一下,equals()是對象相等性比較,hashCode()是計算對象的散列值,固然他們的依據是對象的屬性。數組

 對於equals,通常咱們認爲兩個對象同類型而且全部屬性相等的時候纔是相等的,在類中必須改寫equals,由於Object類中的equals只是判斷兩個引用變量是否引用同一對象,若是不是引用同一對象,即便兩個對象的內容徹底相同,也會返回false。固然,在類中改寫這個equals時,你也能夠只對部分屬性進行比較,只要這些屬性相同就認爲對象是相等的。  
   
  對於hashCode,只要是用在和哈希運算有關的地方,前面不少兄弟都提到了,和equals同樣,在你的類中也應該改寫。固然若是兩個對象是徹底相同的,那麼他們的hashCode固然也是同樣的,可是象前面所述,規則能夠由你本身來定義,所以二者之間並無什麼必然的聯繫。  
   
  固然,大多數狀況下咱們仍是根據全部的屬性來計算hashCode和進行相等性比較。安全

 

 

  HashMap實現了Map接口,該接口的做用主要是爲客戶提供三種方式的數據顯示:只查看keys列表;只查看values列表,或以key-value形式成對查看。Map接口並無定義數據要如何存儲,也沒有指定如何斷定key是同樣,所以並非全部的Map實現都會與hashCode方法扯上關係,如TreeMap即是要求對象實現Comparator接口,經過其compare方法來比對二者是否一致,而非hashCode及equals。同理,若是咱們本身實現Map接口,咱們也能夠直接使用數組進行數據存儲使用==斷定key值是否一致,依然能夠徹底知足Map接口的定義。數據結構

就是一個鍵值對應的集合,包含put,get,equals,hashCode
HashMap a = new HashMap();
  a.put("name", "abcdef"); // key是name,value是字符串abcdef
HashMap是基於HashCode的

Hash容器的基本要求應該有以下幾點:多線程

知足Hash表的查找要求(廢話)
能支持從小數據量到大數據量的自動轉變(自動擴容)
使用掛鏈法解決衝突

 在Hash表中,記錄在表中的位置和其關鍵字之間存在着一種肯定的關係。這樣 咱們就能預先知道所查關鍵字在表中的位置,從而直接經過下標找到記錄。

 

HashSet無序,不可重複,線程非同步。底層是哈希表結構

 

散列表(Hash table,也叫哈希表),是根據關鍵字(Key value)而直接進行訪問的數據結構。
也就是說,它經過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫作散列函數,存放記錄的數組叫作散列表。

HashMap底層就是散列表數據結構,即數組和鏈表的結合體,
底層是一個數組結構,數組中的每一項又是一個鏈表。這樣作有什麼好處呢?
數組可以提供對元素的快速訪問但不易於擴展(若是不知道元素腳標,還得進行遍歷查找),鏈表易於擴展但不能對其元素進行快速訪問。
怎樣作到一箭雙鵰,就是散列表數據結構

 

元素(key,value)在HashMap中被封裝進Entry數組。
put元素的時候,根據key的hash值定位元素在Entry數組中的索引,若是當前索引有元素,就經過equals方法進行比較,將元素存進當前Entry的鏈表中適當的位置。
get元素的時候,根據key的hash值定位元素在Entry數組中的索引,而後經過equals方法定位元素在鏈表中的位置,取出該元素。

性能分析:

由於元素的存取是經過hash算法進行的,因此速度都沒的說。
在查找操做中,惟一影響性能的是在鏈表中,但實際只要優化好了key對象hashCode跟equals方法,就會避免鏈表中的數據過多而致使查找性能變慢。

 

  1. hashcode方法返回該對象的哈希碼值。支持該方法是爲哈希表提供一些優勢,例如,java.util.Hashtable 提供的哈希表。   
  2.   
  3. hashCode 的常規協定是:   
  4. 在 Java 應用程序執行期間,在同一對象上屢次調用 hashCode 方法時,必須一致地返回相同的整數,前提是對象上 equals 比較中所用的信息沒有被修改。從某一應用程序的一次執行到同一應用程序的另外一次執行,該整數無需保持一致。   
  5. 若是根據 equals(Object) 方法,兩個對象是相等的,那麼在兩個對象中的每一個對象上調用 hashCode 方法都必須生成相同的整數結果。   
  6. 如下狀況不 是必需的:若是根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼在兩個對象中的任一對象上調用 hashCode 方法一定會生成不一樣的整數結果。可是,程序員應該知道,爲不相等的對象生成不一樣整數結果能夠提升哈希表的性能。   
  7. 實際上,由 Object 類定義的 hashCode 方法確實會針對不一樣的對象返回不一樣的整數。(這通常是經過將該對象的內部地址轉換成一個整數來實現的,可是 JavaTM 編程語言不須要這種實現技巧。)   
  8.   
  9. 當equals方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。  

 

  1. 1.hashcode是用來查找的,若是你學過數據結構就應該知道,在查找和排序這一章有  
  2. 例如內存中有這樣的位置  
  3. 0  1  2  3  4  5  6  7    
  4. 而我有個類,這個類有個字段叫ID,我要把這個類存放在以上8個位置之一,若是不用hashcode而任意存放,那麼當查找時就須要到這八個位置裏挨個去找,或者用二分法一類的算法。  
  5. 但若是用hashcode那就會使效率提升不少。  
  6. 咱們這個類中有個字段叫ID,那麼咱們就定義咱們的hashcode爲ID%8,而後把咱們的類存放在取得得餘數那個位置。好比咱們的ID爲9,9除8的餘數爲1,那麼咱們就把該類存在1這個位置,若是ID是13,求得的餘數是5,那麼咱們就把該類放在5這個位置。這樣,之後在查找該類時就能夠經過ID除 8求餘數直接找到存放的位置了。  
  7.   
  8. 2.可是若是兩個類有相同的hashcode怎麼辦那(咱們假設上面的類的ID不是惟一的),例如9除以8和17除以8的餘數都是1,那麼這是否是合法的,回答是:能夠這樣。那麼如何判斷呢?在這個時候就須要定義 equals了。  
  9. 也就是說,咱們先經過 hashcode來判斷兩個類是否存放某個桶裏,但這個桶裏可能有不少類,那麼咱們就須要再經過 equals 來在這個桶裏找到咱們要的類。  
  10. 那麼。重寫了equals(),爲何還要重寫hashCode()呢?  
  11. 想一想,你要在一個桶裏找東西,你必須先要找到這個桶啊,你不經過重寫hashcode()來找到桶,光重寫equals()有什麼用啊  

 

哈希算法也稱爲散列算法,是將數據依特定算法直接指定到一個地址上。若是詳細講解哈希算法,那須要更多的文章篇幅,我在這裏就不介紹了。 
初學者能夠這樣理解,hashCode方法實際上返回的就是對象存儲的物理地址(實際可能並非)。   
這樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一會兒能定位到它應該放置的物理位置上。 
若是這個位置上沒有元素,它就能夠直接存儲在這個位置上,不用再進行任何比較了;若是這個位置上已經有元素了, 
就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。 
因此這裏存在一個衝突解決的問題。這樣一來實際調用equals方法的次數就大大下降了,幾乎只須要一兩次。   
因此,Java對於eqauls方法和hashCode方法是這樣規定的: 
一、若是兩個對象相同,那麼它們的hashCode值必定要相同;二、若是兩個對象的hashCode相同,它們並不必定相同     上面說的對象相同指的是用eqauls方法比較。   
你固然能夠不按要求去作了,但你會發現,相同的對象能夠出如今Set集合中。同時,增長新元素的效率會大大降低。

 

Java中的集合(Collection)有兩類,一類是List,再有一類是Set。 
你知道它們的區別嗎?前者集合內的元素是有序的,元素能夠重複;後者元素無序,但元素不可重複。 
那麼這裏就有一個比較嚴重的問題了:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢? 
這就是Object.equals方法了。可是,若是每增長一個元素就檢查一次,那麼當元素不少時,後添加到集合中的元素比較的次數就很是多了。 

 

hashcode這個方法是用來鑑定2個對象是否相等的。
那你會說,不是還有equals這個方法嗎?

不錯,這2個方法都是用來判斷2個對象是否相等的。可是他們是有區別的。

通常來說,equals這個方法是給用戶調用的,若是你想判斷2個對象是否相等,你能夠重寫equals方法,而後在代碼中調用,就能夠判斷他們是否相等了。簡單來說,equals方法主要是用來判斷從表面上看或者從內容上看,2個對象是否是相等。舉個例子,有個學生類,屬性只有姓名和性別,那麼咱們能夠認爲只要姓名和性別相等,那麼就說這2個對象是相等的。

hashcode方法通常用戶不會去調用,好比在hashmap中,因爲key是不能夠重複的,他在判斷key是否是重複的時候就判斷了hashcode這個方法,並且也用到了equals方法。這裏不能夠重複是說equals和hashcode只要有一個不等就能夠了!因此簡單來說,hashcode至關因而一個對象的編碼,就好像文件中的md5,他和equals不一樣就在於他返回的是int型的,比較起來不直觀。咱們通常在覆蓋equals的同時也要覆蓋hashcode,讓他們的邏輯一致。舉個例子,仍是剛剛的例子,若是姓名和性別相等就算2個對象相等的話,那麼hashcode的方法也要返回姓名的hashcode值加上性別的hashcode值,這樣從邏輯上,他們就一致了。

要從物理上判斷2個對象是否相等,用==就能夠了。



1.hashMap去掉了HashTable 的contains方法,可是加上了containsValue()和containsKey()方法。

2.hashTable同步的,線程安全,而HashMap是非同步的,線程不安全,效率上逼hashTable要高。

3.hashMap容許空鍵值,而hashTable不容許。

4.HashTable中hash數組默認大小是11,增長的方式是 old*2+1。HashMap中hash數組的默認大小是16,並且必定是2的指數。

5.Hashtable是Dictionary的子類,HashMap是Map接口的一個實現類;

 

HashMap實現同步的方法:調用Collections的靜態方法Collections.synchronizedMap(Map map);,返回一個同步的Map,這個Map封裝了底層的HashMap的全部方法,使得底層的HashMap即便是在多線程的環境中也是安全的。

 

同時說一下,ArrayList和HashSet,他們也都不是同步的,都是線程不安全的,對其實現同步的方式和HashMap的方式相同,都容許使用null元素,ArrayList分配的初始空間爲10,HashSet分配的初始空間爲16

相關文章
相關標籤/搜索