一個例子看明白hashmap
1. 儲存物件 以及測試code
package com.java.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 90; } @Override public boolean equals(Object obj) { Country other = (Country) obj; if (name.equalsIgnoreCase((other.name))) return true; return false; } }
package com.java.hashmap; import java.util.HashMap; import java.util.Iterator; public class HashMapStructure { /** * @author Arpit Mandliya */ 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"); // countryCapitalMap.put(japan,"beijing"); 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); } } }
code分析,country 類中的hashcode計算僅僅是為了便於研究hashmap的儲存原理,實際不可能這樣計算,因為存在hash衝突。
在此例中,按照hashcode的計算方法,顯然india和japan的hashcode相同,France和Russia的hashcode值相同。前者為31,後者為90.
下一步是儲存,散列表為長度為16的陣列,求模後計算索引值:
31%16=15 France和Russia
90%16=10 india和japan
儲存結構如下圖所示:
通過debug,觀察結構如下:
從上圖可以觀察到以下幾點:
-
有一個叫做table大小是16的Entry陣列。
-
這個table陣列儲存了Entry類的物件。HashMap類有一個叫做Entry的內部類。這個Entry類包含了key-value作為例項變數。我們來看下Entry類的結構。Entry類的結構:
1 2 3 4 5 6 7 8 |
static class Entry
implements Map.Entry
{
final K
key;
V
value;
Entry
next;
final int hash;
... //More
code goes here
}
`
|
-
每當往hashmap裡面存放key-value對的時候,都會為它們例項化一個Entry物件,這個Entry物件就會儲存在前面提到的Entry陣列table中。現在你一定很想知道,上面建立的Entry物件將會存放在具體哪個位置(在table中的精確位置)。答案就是,根據key的hashcode()方法計算出來的hash值(來決定)。hash值用來計算key在Entry陣列的索引。
-
現在,如果你看下上圖中陣列的索引10,它有一個叫做HashMap$Entry的Entry物件。
-
我們往hashmap放了4個key-value對,但是看上去好像只有2個元素!!!這是因為,如果兩個元素有相同的hashcode,它們會被放在同一個索引上。問題出現了,該怎麼放呢?原來它是以連結串列(LinkedList)的形式來儲存的(邏輯上)。
2. Key相同的情況
上面的例子,hashcode相同,如果key相同的情況呢?測試一下:package com.java.hashmap;
import java.util.HashMap;
import java.util.Iterator;
public class HashMapStructure {
/**
* @author Arpit Mandliya
*/
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");
<span style="background-color: rgb(255, 0, 0);"> countryCapitalMap.put(japan,"beijing");</span>
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);
}
}
}
-
在我們的例子中已經看到,如果兩個key有相同的hash值(也叫衝突),他們會以連結串列的形式來儲存。所以,這裡我們就迭代連結串列。
- 如果在剛才計算出來的索引位置沒有元素,直接把Entry物件放在那個索引上。
- 如果索引上有元素,然後會進行迭代,一直到Entry->next是null。當前的Entry物件變成連結串列的下一個節點。
- 如果我們再次放入同樣的key會怎樣呢?邏輯上,它應該替換老的value。事實上,它確實是這麼做的。在迭代的過程中,會呼叫equals()方法來檢查key的相等性(key.equals(k)),如果這個方法返回true,它就會用當前Entry的value來替換之前的value。
當你理解了hashmap的put的工作原理,理解get的工作原理就非常簡單了。當你傳遞一個key從hashmap總獲取value的時候:
-
對key進行null檢查。如果key是null,table[0]這個位置的元素將被返回。
-
key的hashcode()方法被呼叫,然後計算hash值。
-
indexFor(hash,table.length)用來計算要獲取的Entry物件在table陣列中的精確的位置,使用剛才計算的hash值。
-
在獲取了table陣列的索引之後,會迭代連結串列,呼叫equals()方法檢查key的相等性,如果equals()方法返回true,get方法返回Entry物件的value,否則,返回null。
要牢記以下關鍵點:
- HashMap有一個叫做Entry的內部類,它用來儲存key-value對。
- 上面的Entry物件是儲存在一個叫做table的Entry陣列中。
- table的索引在邏輯上叫做“桶”(bucket),它儲存了連結串列的第一個元素。
- key的hashcode()方法用來找到Entry物件所在的桶。
- 如果兩個key有相同的hash值,他們會被放在table陣列的同一個桶裡面。
- key的equals()方法用來確保key的唯一性。
- value物件的equals()和hashcode()方法根本一點用也沒有。