面試的時候常常會碰見諸如:「java中的HashMap是怎麼工做的」,「HashMap的get和put內部的工做原理」這樣的問題。本文將用一個簡單的例子來解釋下HashMap內部的工做原理。首先咱們從一個例子開始,而不單單是從理論上,這樣,有助於更好地理解,而後,咱們來看下get和put究竟是怎樣工做的。java
咱們來看個很是簡單的例子。有一個」國家」(Country)類,咱們將要用Country對象做爲key,它的首都的名字(String類型)做爲value。下面的例子有助於咱們理解key-value對在HashMap中是如何存儲的。面試
1. Country.javaapi
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; } }
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)。將會看到以下的結構:微信
從上圖能夠觀察到如下幾點:app
1. 有一個叫作table大小是16的Entry數組。dom
2. 這個table數組存儲了Entry類的對象。HashMap類有一個叫作Entry的內部類。這個Entry類包含了key-value做爲實例變量。咱們來看下Entry類的結構。Entry類的結構:ide
static class Entry implements Map.Entry { final K key; V value; Entry next; final int hash; ...//More code goes here } `
3. 每當往hashmap裏面存放key-value對的時候,都會爲它們實例化一個Entry對象,這個Entry對象就會存儲在前面提到的Entry數組table中。如今你必定很想知道,上面建立的Entry對象將會存放在具體哪一個位置(在table中的精確位置)。答案就是,根據key的hashcode()方法計算出來的hash值(來決定)。hash值用來計算key在Entry數組的索引。函數
4. 如今,若是你看下上圖中數組的索引10,它有一個叫作HashMap$Entry的Entry對象。post
5. 咱們往hashmap放了4個key-value對,可是看上去好像只有2個元素!!!這是由於,若是兩個元素有相同的hashcode,它們會被放在同一個索引上。問題出現了,該怎麼放呢?原來它是以鏈表(LinkedList)的形式來存儲的(邏輯上)。
上面的country對象的key-value的hash值是如何計算出來的。
<code>Japan的Hash值是95,它的長度是奇數。
India的Hash值是95,它的長度是奇數。
Russia的Hash值是31,它的長度是偶數。
France,它的長度是偶數。
</code>
下圖會清晰的從概念上解釋下鏈表。
因此,如今假如你已經很好地瞭解了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; }
如今咱們一步一步來看下上面的代碼。
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的時候:
要牢記如下關鍵點:
我有一個微信公衆號,常常會分享一些Java技術相關的乾貨。若是你喜歡個人分享,能夠用微信搜索「Java團長」或者「javatuanzhang」關注。