1. 程式人生 > >一個例子看明白hashmap

一個例子看明白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,觀察結構如下:


從上圖可以觀察到以下幾點:

  1. 有一個叫做table大小是16的Entry陣列。

  2. 這個table陣列儲存了Entry類的物件。HashMap類有一個叫做Entry的內部類。這個Entry類包含了key-value作為例項變數。我們來看下Entry類的結構。Entry類的結構:

1 2 3 4 5 6 7 8 staticclassEntry implementsMap.Entry { finalK key; V value; Entry next; finalinthash; ...//More code goes here }   `
  1. 每當往hashmap裡面存放key-value對的時候,都會為它們例項化一個Entry物件,這個Entry物件就會儲存在前面提到的Entry陣列table中。現在你一定很想知道,上面建立的Entry物件將會存放在具體哪個位置(在table中的精確位置)。答案就是,根據key的hashcode()方法計算出來的hash值(來決定)。hash值用來計算key在Entry陣列的索引。

  2. 現在,如果你看下上圖中陣列的索引10,它有一個叫做HashMap$Entry的Entry物件。

  3. 我們往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);
            }
        }
   
   
} 


  1. 在我們的例子中已經看到,如果兩個key有相同的hash值(也叫衝突),他們會以連結串列的形式來儲存。所以,這裡我們就迭代連結串列。

  • 如果在剛才計算出來的索引位置沒有元素,直接把Entry物件放在那個索引上。
  • 如果索引上有元素,然後會進行迭代,一直到Entry->next是null。當前的Entry物件變成連結串列的下一個節點。
  • 如果我們再次放入同樣的key會怎樣呢?邏輯上,它應該替換老的value。事實上,它確實是這麼做的。在迭代的過程中,會呼叫equals()方法來檢查key的相等性(key.equals(k)),如果這個方法返回true,它就會用當前Entry的value來替換之前的value。

當你理解了hashmap的put的工作原理,理解get的工作原理就非常簡單了。當你傳遞一個key從hashmap總獲取value的時候:

  1. 對key進行null檢查。如果key是null,table[0]這個位置的元素將被返回。

  2. key的hashcode()方法被呼叫,然後計算hash值。

  3. indexFor(hash,table.length)用來計算要獲取的Entry物件在table陣列中的精確的位置,使用剛才計算的hash值。

  4. 在獲取了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()方法根本一點用也沒有。