Java HashMap的工作原理
面試的時候經常會遇見諸如:“java中的HashMap是怎麼工作的”,“HashMap的get和put內部的工作原理”這樣的問題。本文將用一個簡單的例子來解釋下HashMap內部的工作原理。首先我們從一個例子開始,而不僅僅是從理論上,這樣,有助於更好地理解,然後,我們來看下get和put到底是怎樣工作的。
我們來看個非常簡單的例子。有一個”國家”(Country)類,我們將要用Country物件作為key,它的首都的名字(String型別)作為value。下面的例子有助於我們理解key-value對在HashMap中是如何儲存的。
1. Country.java
package org.arpit.javapostsforlearning;
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;
}}
如果想了解更多關於Object物件的hashcode和equals方法的東西,可以參考:
2. HashMapStructure.java(main class)
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”);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);
}
}}
現在,在第23行設定一個斷點,在專案上右擊->除錯執行(debug as)->java應用(java application)。程式會停在23行,然後在countryCapitalMap上右擊,選擇“檢視”(watch)。將會看到如下的結構:
從上圖可以觀察到以下幾點:
- 有一個叫做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
}
- 每當往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)的形式來儲存的(邏輯上)。
上面的country物件的key-value的hash值是如何計算出來的。
Japan的Hash值是95,它的長度是奇數。 India的Hash值是95,它的長度是奇數。 Russia的Hash值是31,它的長度是偶數。 France,它的長度是偶數。
下圖會清晰的從概念上解釋下連結串列。
所以,現在假如你已經很好地瞭解了hashmap的結構,讓我們看下put和get方法。
Put :
讓我們看下put方法的實現:
/**
* Associates the specified value with the specified key in this map. If the
* map previously contained a mapping for the key, the old value is
* replaced.
*
* @param key
* key with which the specified value is to be associated
* @param value
* value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or <tt>null</tt>
* if there was no mapping for <tt>key</tt>. (A <tt>null</tt> return
* can also indicate that the map previously associated
* <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<k , V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}modCount++;
addEntry(hash, key, value, i);
return null;
}
現在我們一步一步來看下上面的程式碼。
- 對key做null檢查。如果key是null,會被儲存到table[0],因為null的hash值總是0。
- key的hashcode()方法會被呼叫,然後計算hash值。hash值用來找到儲存Entry物件的陣列的索引。有時候hash函式可能寫的很不好,所以JDK的設計者新增了另一個叫做hash()的方法,它接收剛才計算的hash值作為引數。如果你想了解更多關於hash()函式的東西,可以參考:hashmap中的hash和indexFor方法
- indexFor(hash,table.length)用來計算在table陣列中儲存Entry物件的精確的索引。
- 在我們的例子中已經看到,如果兩個key有相同的hash值(也叫衝突),他們會以連結串列的形式來儲存。所以,這裡我們就迭代連結串列。
- 如果在剛才計算出來的索引位置沒有元素,直接把Entry物件放在那個索引上。
- 如果索引上有元素,然後會進行迭代,一直到Entry->next是null。當前的Entry物件變成連結串列的下一個節點。
- 如果我們再次放入同樣的key會怎樣呢?邏輯上,它應該替換老的value。事實上,它確實是這麼做的。在迭代的過程中,會呼叫equals()方法來檢查key的相等性(key.equals(k)),如果這個方法返回true,它就會用當前Entry的value來替換之前的value。
Get:
現在我們來看下get方法的實現:
/**
* Returns the value to which the specified key is mapped, or {@code null}
* if this map contains no mapping for the key.
*
* <p>
* More formally, if this map contains a mapping from a key {@code k} to a
* value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* </p><p>
* A return value of {@code null} does not <i>necessarily</i> indicate that
* the map contains no mapping for the key; it’s also possible that the map
* explicitly maps the key to {@code null}. The {@link #containsKey
* containsKey} operation may be used to distinguish these two cases.
*
* @see #put(Object, Object)
*/
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<k , V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
當你理解了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()方法根本一點用也沒有。
原文連結: javacodegeeks 翻譯: ImportNew - miracle1919
相關文章
- java面試題-HashMap的工作原理Java面試題HashMap
- [翻譯]Java HashMap工作原理JavaHashMap
- HashMap的工作原理HashMap
- Java HashMap工作原理深入探討JavaHashMap
- Java HashMap工作原理及實現JavaHashMap
- HashMap工作原理(轉載)HashMap
- 原始碼的魅力 – HashMap 的工作原理原始碼HashMap
- 原始碼的魅力 - HashMap 的工作原理原始碼HashMap
- [java]HashMap原理剖析JavaHashMap
- 乾貨:HashMap的工作原理解析HashMap
- HashMap中面試常問的工作原理HashMap面試
- Java中HashMap的實現原理JavaHashMap
- Java HashMap 的實現原理詳解JavaHashMap
- Java:HashMap原理與設計緣由JavaHashMap
- Java8 HashMap實現原理探究JavaHashMap
- HashMap原理HashMap
- 深入理解Java中的HashMap的實現原理JavaHashMap
- Java java.util.HashMap實現原理原始碼分析JavaHashMap原始碼
- Java集合詳解(三):HashMap原理解析JavaHashMap
- HashMap的底層原理HashMap
- HashMap的實現原理HashMap
- 從程式碼層讀懂 Java HashMap 的實現原理JavaHashMap
- Java HashMap原理及內部儲存結構JavaHashMap
- HashMap的底層原理分析HashMap
- HashMap原理21問HashMap
- HashMap實現原理HashMap
- HashMap擴容原理HashMap
- Java進階:HashMap底層原理(通俗易懂篇)JavaHashMap
- Java Servlet工作原理問答JavaServlet
- Java Servlet 工作原理問答JavaServlet
- java中hashMap的排序JavaHashMap排序
- HashMap原理底層剖析HashMap
- JDK1.7-HashMap原理JDKHashMap
- HashMap原理詳解,包括底層原理HashMap
- Java集合:HashMapJavaHashMap
- Java ArrayDeque工作原理及實現Java
- Java JUnit框架裡@Category註解的工作原理Java框架Go
- 淺談Java中的HashmapJavaHashMap