1. 程式人生 > >hashCode()與equals()區別

hashCode()與equals()區別

  這兩個方法均是超類Object自帶的成員方法。Object類是所有Java類的祖先。每個類都使用 Object 作為超類。所有物件(包括陣列)都實現這個類的方法。在不明確給出超類的情況下,Java會自動把Object作為要定義類的超類。可以使用型別為Object的變數指向任意型別的物件。Object類有一個預設構造方法pubilc Object(),在構造子類例項時,都會先呼叫這個預設構造方法。Object類的變數只能用作各種值的通用持有者。要對他們進行任何專門的操作,都需要知道它們的原始型別並進行型別轉換。例如:

  Object obj = new MyObject();
  MyObject x = (MyObject)obj;

  

  Java語言規範要求equals方法具有下面的特點:
  自反性:對於任何非空引用值 x,x.equals(x) 都應返回 true。
  對稱性:對於任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true。
  傳遞性:對於任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,並且 y.equals(z) 返回 true,那麼 x.equals(z) 應返回 true。
  一致性:對於任何非空引用值 x 和 y,多次呼叫 x.equals(y) 始終返回 true 或始終返回 false,前提是物件上 equals 比較中所用的資訊沒有被修改。
對於任何非空引用值 x,x.equals(null) 都應返回 false。  

  equals()是對兩個物件的地址值進行的比較(即比較引用是否相同)。

  hashCode()是一個本地方法,它的實現是根據本地機器相關的。  

  JVM每new一個Object,它都會將這個Object丟到一個Hash雜湊表中去,這樣的話,下次做Object的比較或者取這個物件的時候,它會根據物件的hashcode再從Hash表中取這個物件。這樣做的目的是提高取物件的效率。具體過程是這樣:

  1. new Object(),JVM根據這個物件的Hashcode值,放入到對應的Hash表對應的Key上,如果不同的物件產生了相同的hash值,也就是發生了Hash key相同導致衝突的情況,那麼就在這個Hash key的地方產生一個連結串列,將所有產生相同hashcode的物件放到這個單鏈表上去,串在一起。

  2. 比較兩個物件的時候,首先根據他們的hashcode去hash表中找他的物件,當兩個物件的hashcode相同,那麼就是說他們這兩個物件放在Hash表中的同一個key上,那麼他們一定在這個key上的連結串列上。那麼此時就只能根據Object的equal方法來比較這個物件是否equal。當兩個物件的hashcode不同的話,肯定他們不能equals.

  list是可以重複的,set是不可以重複的。那麼set儲存資料的時候是怎樣判斷存進的資料是否已經存在。使用equals()方法呢,還是hashcode()方法。

  假如用equals(),那麼儲存一個元素就要跟已存在的所有元素比較一遍,比如已存入100個元素,那麼存101個元素的時候,就要呼叫equals方法100次。

  但如果用hashcode()方法的話,他就利用了hash演算法來儲存資料的。

  這樣的話每存一個數據就呼叫一次hashcode()方法,得到一個hashcode值及存入的位置。如果該位置不存在資料那麼就直接存入,否則呼叫一次equals()方法,不相同則存,相同不存。這樣下來整個儲存下來不需要呼叫幾次equals方法,雖然多了幾次hashcode方法,但相對於前面來講效率高了不少。

  因為Object的equal方法預設是兩個物件的引用的比較,意思就是指向同一記憶體,地址則相等,否則不相等;如果你現在需要利用物件裡面的值來判斷是否相等,則過載equal方法。

  JDK中,String、Math等封裝類都對Object中的equals()方法進行了重寫。

  java.lnag.Object中對hashCode的約定:

  1. 在一個應用程式執行期間,如果一個物件的equals方法做比較所用到的資訊沒有被修改的話,則對該物件呼叫hashCode方法多次,它必須始終如一地返回同一個整數。
  2. 如果兩個物件根據equals(Object o)方法是相等的,則呼叫這兩個物件中任一物件的hashCode方法必須產生相同的整數結果。
  3. 如果兩個物件根據equals(Object o)方法是不相等的,則呼叫這兩個物件中任一個物件的hashCode方法,不要求產生不同的整數結果。但如果能不同,則可能提高散列表的效能。

  Java中的Collection有兩類,一類是List,一類是Set。List內的元素是有序的,元素可以重複。Set元素無序,但元素不可重複。要想保證元素不重複,兩個元素是否重複應該依據什麼來判斷呢?用Object.equals方法。但若每增加一個元素就檢查一次,那麼當元素很多時,後新增到集合中的元素比較的次數就非常多了。也就是說若集合中已有1000個元素,那麼第1001個元素加入集合時,它就要呼叫1000次equals方法。這顯然會大大降低效率。於是Java採用了雜湊表的原理。

  我們知道,陣列和向量都可以儲存物件,但物件的儲存位置是隨機的,也就是說物件本身與其儲存位置之間沒有必然的聯絡。當要查詢一個物件時,只能以某種順序(如順序查詢或二分查詢)與各個元素進行比較,當陣列或向量中的元素數量很多時,查詢的效率會明顯地降低。

  一種有效的儲存方式,是不與其他元素進行比較,一次存取便能得到所需要的記錄。這就需要在物件的儲存位置和物件的關鍵屬性(設為 k)之間建立一個特定的對應關係(設為 f),使每個物件與一個唯一的儲存位置相對應。在查詢時,只要根據待查物件的關鍵屬性 k 計算f(k)的值即可。如果此物件在集合中,則必定在儲存位置 f(k)上,因此不需要與集合中的其他元素進行比較。稱這種對應關係 f 為雜湊(hash)方法,按照這種思想建立的表為雜湊表。

  HashSet底層是用了HashMap。

  我們在此需要理解幾個概念:

1.雜湊演算法,是一類演算法:這類演算法接受任意長度的二進位制輸入值,對輸入值做換算(如切碎),最終給出固定長度的二進位制輸出值。
所以,Hash演算法不是某個固定的演算法,它代表的是一類演算法。以更好理解的方式來說,Hash演算法是摘要演算法,也就是說,從不同的輸入中,通過一些計算摘取出來一段輸出資料。
給密碼加密用的MD5 就是一種Hash演算法 。
那麼,具體來說,Hash/摘要/雜湊/切碎演算法都有哪些用處呢?
(1)資訊保安領域:
Hash演算法可用作加密演算法。如檔案校驗,通過對檔案摘要,可以得到檔案的“數字指紋”,你下載的任何副本的“數字指紋”只要與官方給出的“數字指紋”一致,那麼就可以知道這是未經篡改的。例如著名的MD5 。
(2)資料結構領域:
Hash演算法 通常還可用作快速查詢。根據Hash函式,我們可以實現一種叫做雜湊表(Hash Table)的資料結構。這種結構可以實現對資料進行快速的存取。
2.雜湊表(Hash Table)是一種資料結構。
相對來說,線性表、樹 這些資料結構中,記錄 在結構 中的相對位置是隨機的,和記錄的關鍵字之間不存在確定關係,因此,在資料結構中查詢時需要進行一系列和關鍵字的比較。這一類查詢方法建立在“比較”的基礎上。在順序查詢時,比較的結果為“=”與“≠”2種可能;在折半查詢、二叉排序樹查詢和B-樹查詢時,比較的結果為“<”“=”“>”3種可能。查詢的效率依賴於查詢過程中所進行的比較次數。
理想的情況是希望不經過任何比較,一次存取便能得到所查記錄,那就必須在記錄的儲存位置和它的關鍵字之間建立一個確定的關係,使每個關鍵字和結構中一個唯一的儲存位置相對應。因而在查詢時,只要根據這個對應關係找到給定值即可。若結構中存在關鍵字和相等的記錄,則必定在該儲存位置上,反之在這個位置上沒有記錄。由此,不需要比較便可直接取得所查記錄。在此,我們稱這個對應關係為雜湊(Hash)函式 ,按這個思想建立的表為雜湊表 。
雜湊函式有兩個特點:
(1)靈活。雜湊函式是一個映像,因此雜湊函式的設定很靈活,只要使得任何關鍵字由此所得的雜湊函式值都落在表長允許的範圍之內即可。
(2)衝突。對不同的關鍵字可能得到同一雜湊地址,這種現象稱為衝突(collision)。衝突只能儘量地少,而不能完全避免。因為,雜湊函式是從關鍵字集合到地址集合的映像。而通常關鍵字集合比較大,它的元素包括所有可能的關鍵字,而地址集合的元素僅為雜湊表中的地址值。因此,在實現雜湊表這種資料結構的時候不僅要設定一個“好”的雜湊函式,而且要設定一種處理衝突的方法。
由此得出雜湊表的概念:根據設定的Hash函式和處理衝突的方法,將一組關鍵字映象 到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的象 作為記錄在表中的儲存位置,這樣的表便稱為Hash表。
3.雜湊函式,是支撐雜湊表的一類函式。
常用的Hash函式 構造方法有:
(1)直接定址法。取k 或k 的某個線性函式為Hash地址 。特點:由於直接地址法相當於有多少個關鍵字就必須有多少個相應地址去對應,所以不會產生衝突,也正因為此,所以實際中很少使用這種構造方法。
(2)數字分析法。就是找出關鍵字 的規律,儘可能用差異資料來構造Hash地址。特點:需要提前知道所有可能的關鍵字,才能分析運用此種方法,所以不太常用。
(3)平方取中法。先求出關鍵字的平方值,然後按需要取平方值的中間幾位作為雜湊地址。這是因為:平方後中間幾位和關鍵字中每一位都相關,故不同關鍵字會以較高的概率產生不同的雜湊地址。例:我們把英文字母在字母表中的位置序號作為該英文字母的內部編碼。例如K的內部編碼為11,E的內部編碼為05,Y的內部編碼為25,A的內部編碼為01, B的內部編碼為02。由此組成關鍵字“KEYA”的內部程式碼為11052501,同理我們可以得到關鍵字“KYAB”、“AKEY”、“BKEY”的內部編碼。之後對關鍵字進行平方運算後,取出第7到第9位作為該關鍵字雜湊地址。特點:較常用。
(4)摺疊法。將關鍵字分割成位數相同的幾部分(最後一部分位數可以不同),然後取這幾部分的疊加和(去除進位)作為雜湊地址。數位疊加可以有移位疊加和間界疊加兩種方法。移位疊加是將分割後的每一部分的最低位對齊,然後相加;間界疊加是從一端向另一端沿分割界來回摺疊,然後對齊相加。
(5)隨機數法。選擇一個隨機函式,取關鍵字的隨機函式值作為Hash地址 ,通常用於關鍵字長度不同的場合。特點:通常,關鍵字長度不相等時,採用此法構建Hash函式 較為合適。
(6)除留取餘法。取關鍵字被某個不大於Hash表 長m 的數p 除後所得的餘數為Hash地址 。特點:這是最簡單也是最常用的Hash函式構造方法。可以直接取模,也可以在平方取中法、摺疊法之後再取模。值得注意的是,在使用除留取餘法 時,對p 的選擇很重要,如果p 選的不好會容易產生同義詞 。由經驗得知:p 最好選擇不大於表長m的一個質數 、或者不包含小於20的質因數的合數。
如何處理衝突是雜湊造表不可缺少的一個方面。現在完整的描述一下處理衝突:
衝突是指由關鍵字得到的雜湊地址的位置上已存有記錄,則“處理衝突”就是為該關鍵字的記錄找到另一個“空”的雜湊地址。 
在處理衝突的過程中可能得到一個地址序列。即在處理雜湊地址的衝突時,若得到的另一個雜湊地址仍然發生衝突,則再求下一個地址,若仍然衝突,再求,依次類推,直至不發生衝突為止,則為記錄在表中的地址。 
處理衝突通常有以下4種方法:
(1)開放定址法。
若 為雜湊函式,則為雜湊表表長;。若為增量序列,則有3種取法:線性探測再雜湊;二次探測再雜湊;偽隨機探測再雜湊。
(2)再雜湊法。
 均是不同的雜湊函式,即在同義詞產生地址衝突時計算另一個雜湊函式地址,直到衝突不再發生,這種方法不易產生聚集 ,但增加了計算時間。
(3)鏈地址法。
將所有關鍵字為同義詞的記錄儲存在同一線性表中,即在Hash 出來的雜湊地址中不直接存Key ,而是儲存一個Key 的連結串列 ,當發生衝突 時,將同義的Key 加入連結串列。
(4)公共溢位區。
可以建立一個公共溢位區,用來存放有衝突的Key 。比如設立另一個雜湊表,專門用來存放出現衝突的同義詞。
4.Map是對映、地圖的意思,在Java中Map表示一種把K對映到V的資料型別;
HashMap是Java中用雜湊資料結構實現的Map。
資料結構表達的是:用什麼樣的結構,組織一類資料。資料結構分為邏輯結構和物理結構:
(1)基本的邏輯結構有:集合、線性結構、樹形結構、圖。
(2)物理結構:順序儲存、鏈式儲存。
Hash表 是一種邏輯資料結構,HashMap是Java中的一種資料型別(結構型別),它通過程式碼實現了Hash表 這種資料結構,並在此結構上定義了一系列操作。
HashMap是基於陣列來實現雜湊表的,陣列就好比記憶體儲空間,陣列的index就好比記憶體的地址。
HashMap的每個記錄就是一個Entry<K, V>物件,陣列中儲存的就是這些物件。
HashMap的雜湊函式 = 計算出hashCode + 計算出陣列的index。
HashMap解決衝突:使用鏈地址法,每個Entry物件都有一個引用next來指向連結串列的下一個Entry。
HashMap的裝填因子:預設為0.75。【裝填因子標誌雜湊表的裝滿程度。裝填因子越小,發生衝突的可能性就越小;越大,代表著表中已填入的元素越多,再填入元素時發生衝突的可能性就越大,那麼在查詢時,給定值需要比較的關鍵字的個數就越多】