HashMap的hash沖突解決方案
Hash函數
非哈希表的特點:關鍵字在表中的位置和它之間不存在一個確定的關系,查找的過程為給定值一次和各個關鍵字進行比較,查找的效率取決於和給定值進行比較的次數。
哈希表的特點:關鍵字在表中位置和它之間存在一種確定的關系。
哈希函數:一般情況下,需要在關鍵字與它在表中的存儲位置之間建立一個函數關系,以f(key)作為關鍵字為key的記錄在表中的位置,通常稱這個函數f(key)為哈希函數。
hash : 翻譯為“散列”,就是把任意長度的輸入,通過散列算法,變成固定長度的輸出,該輸出就是散列值。
這種轉換是一種壓縮映射,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來唯一的確定輸入值。
簡單的說就是一種將任意長度的消息壓縮到莫伊固定長度的消息摘要的函數。
hash沖突:就是根據key即經過一個函數f(key)得到的結果的作為地址去存放當前的key value鍵值對(這個是hashmap的存值方式),但是卻發現算出來的地址上已經有人先來了。就是說這個地方被搶了啦。這就是所謂的hash沖突啦。
哈希函數處理沖突的方法
1.開放定址法:
其中 m 為表的長度
對增量di有三種取法:
線性探測再散列 di = 1 , 2 , 3 , ... , m-1
平方探測再散列 di = 1 2 , -2 , 4 , -4 , 8 , -8 , ... , k的平方 , -k平方
隨機探測再散列 di 是一組偽隨機數列
2.鏈地址法
這種方法的基本思想是將所有哈希地址為i的元素構成一個稱為同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況。
3.再哈希
這種方法是同時構造多個不同的哈希函數:
Hi=RH1(key) i=1,2,…,k
當哈希地址Hi=RH1(key)發生沖突時,再計算Hi=RH2(key)……,直到沖突不再產生。這種方法不易產生聚集,但增加了計算時間。
4.建立公共溢出區
這種方法的基本思想是:將哈希表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表
HashMap的Hash沖突處理辦法
hashmap出現了Hash沖突的時候采用第二種辦法:鏈地址法。
代碼示例:
有一個”國家”(Country)類,我們將要用Country對象作為key,它的首都的名字(String類型)作為value。下面的例子有助於我們理解key-value對在HashMap中是如何存儲的。
public class Country { String name; long population; public Country(String name, long population) { super(); this.name = name; this.population = population; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getPopulation() { return population; } public void setPopulation(long population) { this.population = population; } // If length of name in country object is even then return 31(any random // number) and if odd then return 95(any random number). // This is not a good practice to generate hashcode as below method but I am // doing so to give better and easy understanding of hashmap. @Override public int hashCode() { if (this.name.length() % 2 == 0) return 31; else return 95; } @Override public boolean equals(Object obj) { Country other = (Country) obj; if (name.equalsIgnoreCase((other.name))) return true; return false; } }
public class HashMapStructure { public static void main(String[] args) { Country india = new Country("India", 1000); Country japan = new Country("Japan", 10000); Country france = new Country("France", 2000); Country russia = new Country("Russia", 20000); HashMap<Country, String> countryCapitalMap = new HashMap<Country, String>(); countryCapitalMap.put(india, "Delhi"); countryCapitalMap.put(japan, "Tokyo"); countryCapitalMap.put(france, "Paris"); countryCapitalMap.put(russia, "Moscow"); Iterator<Country> countryCapitalIter = countryCapitalMap.keySet().iterator();// put debug point at this line while (countryCapitalIter.hasNext()) { Country countryObj = countryCapitalIter.next(); String capital = countryCapitalMap.get(countryObj); System.out.println(countryObj.getName() + "----" + capital); } }
}
在註釋處加入debug,可以通過watch查看countryCapitalMap的結構:
從上圖可以觀察到以下幾點:
-
有一個叫做table大小是16的Entry數組。
-
這個table數組存儲了Entry類的對象。HashMap類有一個叫做Entry的內部類。這個Entry類包含了key-value作為實例變量。我們來看下Entry類的結構。Entry類的結構:
static class Entry implements Map.Entry{ final K key; V value; Entry next; final int hash; ...//More code goes here }
1).每當往hashmap裏面存放key-value對的時候,都會為它們實例化一個Entry對象,這個Entry對象就會存儲在前面提到的Entry數 組table中。現在你一定很想知道,上面創建的Entry對象將會存放在具體哪個位置(在table中的精確位置)。答案就是,根據key的 hashcode()方法計算出來的hash值(來決定)。hash值用來計算key在Entry數組的索引。
2).現在,如果你看下上圖中數組的索引15,它有一個叫做HashMap$Entry的Entry對象。
3).我們往hashmap放了4個key-value對,但是看上去好像只有1個元素!!!這是因為,如果兩個元素有相同的hashcode,它們會 被放在同一個索引上。問題出現了,該怎麽放呢?原來它是以鏈表(LinkedList)的形式來存儲的(邏輯上)。因此他們都在hash值為15的位置 上存著了,然後把多個Entry,用next進行鏈接。
HashMap的hash沖突解決方案