1. 程式人生 > >一篇文章讓你真正讀懂HashMap

一篇文章讓你真正讀懂HashMap

前言------

在使用HashMap的過程中,是否對key值不能重複有過疑問? 是否對改原因百思不得其解? 是否只會使用常用方法?  是否很想理解hash演算法為基礎的HashMap?  如果有, 那麼恭喜你哈, 找對文章了!

歡迎轉載,轉載請註明來源

 

目錄

一.讀這篇文章之前,  請先充電(重中之重)

二. 通過四個例子, 讀懂put(key ,value)方法的原始碼

三. 理一理思路


一.讀這篇文章之前,  請先充電(重中之重)

讀者可以選擇性充電 , 但是1. 2是必須得會的,重中之重,不然根本就理解不來下面要講的!

1.關於hash演算法: http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html

2.關於equals()和hashCode的知識:https://blog.csdn.net/CCSGTC/article/details/84344111

3.關於HashMap的一些基本操作:https://blog.csdn.net/CCSGTC/article/details/83866484

其實HashMap就是一個 Entry[] table+ 拉鍊法 的雜湊

 

 

二. 通過四個例子, 讀懂put(key ,value)方法的原始碼

大家可以清楚地看到, 這邊的原始碼用到了hashCode()和equals(),  接下來我們就按hashCode()和equals()來進行討論,看看各種

情況下都會發生什麼:

重寫euqls(): ID和Brand相同的car會被視為相同的car

重寫hashCode(), ID和Brand相同的car的hashCode值會相同

 

  • 沒有重寫equals(),也沒有重寫hashCode()
import java.util.HashMap;

public class HashMapTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub


		HashMap <Car, String> myMap = new HashMap <Car,String>();
		
		Car car1 = new Car("1222","AA");
		Car car2 = new Car("1222","AA");
		

		myMap.put(car1, "Jane1");
		myMap.put(car2, "Jane2");

		
		//判斷是否存在value為Jane1的Entry
		System.out.println(myMap.containsValue("Jane1"));
		
		//判斷是否蹲在value為Jane2的Entry
		System.out.println(myMap.containsValue("Jane2"));

		

	}
	

}
class Car{
	
	
	String ID;//車牌號
	String Brand;//品牌
	
	public Car(String ID,String Brand) {
		
		this.ID = ID;
		this.Brand = Brand;
	}

	
	
	
}

測試結果:

true

true

分析流程:

當我們put (car1 ,"Jane1") ,時:

(1).首先計算car1對應的hashCode值,然後 根據hashCode值和table.length計算key對應的hash值, 接著計算這個hash值對應的下邊Index, 也就是求所在的 連結串列是哪一條(根據table[Index]進行訪問) ,假設所在的連結串列是table[3]

(2)  Entry<car1 ,"Jane1"> 是否能進到table[3]所在的連結串列,還要看下面的判斷

(3) 發現了table[3]上沒有Entry,所以不用判斷table[3]這條連結串列上是否有key和car1重複了,直接把Entry<car1,"Jane1">掛上去

當我們put(car2, "Jane2")時:

(1).首先計算car2對應的hashCode值, 由於沒有重寫hashCode, car1和car2的值是不同的,所以它們的hash值也是不同的,所以

car2所對應的連結串列也和car1不同,這邊假設為table[6]

(2)由於table[6]上的連結串列也是空連結串列,所以Entry<car2, "Jane2">也是直接就可以掛在table[6]上了,無需判斷連結串列上是否有key與之重複.

最後的連結串列圖就是:

 

 

  • 重寫了euqlas(), 沒有重寫hashCode()
import java.util.HashMap;

public class HashMapTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub


		HashMap <Car, String> myMap = new HashMap <Car,String>();
		
		Car car1 = new Car("1222","AA");
		Car car2 = new Car("1222","AA");
		

		myMap.put(car1, "Jane1");
		myMap.put(car2, "Jane2");

		
		//判斷是否存在value為Jane1的Entry
		System.out.println(myMap.containsValue("Jane1"));
		
		//判斷是否蹲在value為Jane2的Entry
		System.out.println(myMap.containsValue("Jane2"));

		

	}
	

}
class Car{
	
	
	String ID;//車牌號
	String Brand;//品牌
	
	public Car(String ID,String Brand) {
		
		this.ID = ID;
		this.Brand = Brand;
	}

    
	
	public boolean equals(Object obj) { //定義ID和Brand都相同的物件就是相同的
		
		return   (ID ==((Car)obj).ID ) && (Brand ==((Car)obj).Brand);
	}
	


	
	
	
}

測試結果:

true
true

分析流程:

當我們put (car1 ,"Jane1") ,時:

(1).首先計算car1對應的hashCode值, 然後根據hashCode值和table.length計算key對應的hash值,接著計算這個hash值對應的下邊Index, 也就是求所在的 連結串列是哪一條(根據table[Index]進行訪問) ,假設所在的連結串列是table[3]

(2)  Entry<car1 ,"Jane1"> 是否能進到table[3]所在的連結串列,還要看下面的判斷

(3) 發現了table[3]上沒有Entry,所以不用判斷table[3]這條連結串列上是否有key和car1重複了,直接把Entry<car1,"Jane1">掛上去

當我們put(car2, "Jane2")時:

(1).首先計算car1對應的hashCode值, 由於沒有重寫hashCode, car1和car2的值是不同的,所以它們的hash值也是不同的,所以

car2所對應的連結串列也和car1不同,這邊假設為table[6]

(2)由於table[6]上的連結串列也是空連結串列,所以Entry<car2, "AA">也是直接就可以掛在table[6]上了,無需判斷該連結串列上是否有key與之重複.

最後的連結串列圖也是:

 

 

  • 沒有重寫equals(),  重寫了hashCode()
import java.util.HashMap;

public class HashMapTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub


		HashMap <Car, String> myMap = new HashMap <Car,String>();
		
		Car car1 = new Car("1222","AA");
		Car car2 = new Car("1222","AA");
		

		myMap.put(car1, "Jane1");
		myMap.put(car2, "Jane2");

		
		//判斷是否存在value為Jane1的Entry
		System.out.println(myMap.containsValue("Jane1"));
		
		//判斷是否蹲在value為Jane2的Entry
		System.out.println(myMap.containsValue("Jane2"));

		

	}
	

}
class Car{
	
	
	String ID;//車牌號
	String Brand;//品牌
	
	public Car(String ID,String Brand) {
		
		this.ID = ID;
		this.Brand = Brand;
	}

    



	public int hashCode() {
		
		return ID.hashCode() + Brand.hashCode();
		
	}

	
	
	
}

測試結果:

true
true
 

分析流程:

當我們put (car1 ,"Jane1") ,時:

(1).首先計算car1對應的hashCode值,然後 根據hashCode值和table.length計算key對應的hash值,接著計算這個hash值對應的下邊Index, 也就是求所在的 連結串列是哪一條(根據table[Index]進行訪問) ,假設所在的連結串列是table[3]

(2)  Entry<car1 ,"Jane1"> 是否能進到table[3]所在的連結串列,還要看下面的判斷

(3) 發現了table[3]上沒有Entry,所以不用判斷table[3]這條連結串列上是否有key和car1重複了,直接把Entry<car1,"Jane1">掛上去

當我們put(car2, "Jane2")時:

(1).首先計算car1對應的hashCode值, 由於重寫了hashCode, car1和car2的hashCode值是相同的,它們的hash值也是相同的,所以

car2所對應的連結串列也是相同,都是table[3]這條連結串列

(2) Entry<car2 ,"Jane2"> 是否能進到table[3]所在的連結串列,還要看下面的判斷

(3)由原始碼可以知道, 會開始遍歷table[3]這條連結串列, 判斷key是否重複, 這邊用來判斷的依據就是equals() .但是由於沒有重寫equals(), car2.equals(car1)的結果是false, 也就是car1和car2是被判定為不同的兩個物件,因此,Entry<car2, "AA">也會被掛上 table[3]

最後的連結串列圖如下:

 

 

 

  • 重寫equals() ,也重寫了hashCode() (正確做法,我們必須同時重寫!!)
import java.util.HashMap;

public class HashMapTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub


		HashMap <Car, String> myMap = new HashMap <Car,String>();
		
		Car car1 = new Car("1222","AA");
		Car car2 = new Car("1222","AA");
		

		myMap.put(car1, "Jane1");
		myMap.put(car2, "Jane2");

		
		//判斷是否存在value為Jane1的Entry
		System.out.println(myMap.containsValue("Jane1"));
		
		//判斷是否蹲在value為Jane2的Entry
		System.out.println(myMap.containsValue("Jane2"));

		

	}
	

}
class Car{
	
	
	String ID;//車牌號
	String Brand;//品牌
	
	public Car(String ID,String Brand) {
		
		this.ID = ID;
		this.Brand = Brand;
	}

    

	public boolean equals(Object obj) { //定義ID和Brand都相同的物件就是相同的
		
		return   (ID ==((Car)obj).ID ) && (Brand ==((Car)obj).Brand);
	}



	public int hashCode() {
		
		return ID.hashCode() + Brand.hashCode();
		
	}

	
	
	
}

測試結果:

false

true

 

分析流程:

當我們put (car1 ,"Jane1") 時:

(1).首先計算car1對應的hashCode值, 然後 根據hashCode值和table.length計算key對應的hash值,接著計算這個hash值對應的下邊Index, 也就是求所在的 連結串列是哪一條(根據table[Index]進行訪問) ,假設所在的連結串列是table[3]

(2)  Entry<car1 ,"Jane1"> 是否能進到table[3]所在的連結串列,還要看下面的判斷

(3) 發現了table[3]上沒有Entry,所以不用判斷table[3]這條連結串列上是否有key和car1重複了,直接把Entry<car1,"Jane1">掛上去

當我們put(car2, "Jane2")時:

(1).首先計算car1對應的hashCode值, 由於重寫了hashCode, car1和car2的hashCode值是相同的,它們的hash值也是相同的,所以

car2所對應的連結串列也是相同,都是table[3]這條連結串列

(2) Entry<car2 ,"Jane2"> 是否能進到table[3]所在的連結串列,還要看下面的判斷

(3)由原始碼可以知道, 會開始遍歷table[3]這條連結串列, 判斷key是否重複. 這邊用來判斷的依據就是equals() .由於重寫了equals(), car2.equals(car1)的結果是true, 也就是car1和car2是被判定為相同的兩個物件, 因此key值重複了, 所以就把<car1,"Jane1">替換成<car1,"Jane2"> ,  <car1, "Jane1">就被覆蓋掉了

最後的連結串列圖如下:

 

 

三. 理一理思路
 

(1)每個Entry的hashcode值取決於key的hashcode值

(2)每個Entry有一個hash值,HashMap裡面有個hash()函式,hashcode值相同,hash值就相同

(3)hash值相同的Entry才會放在同一個連結串列上


(4)對於hashcode()的重寫,必須設計一種演算法,讓ID和Brand相同的car可以有相同的hashcode值

(5) 如果不同時重寫hashCode()和equals(), 也會導致<car1, "Jane1> 和<car2, "Jane2">同時處在連結串列上,這就導致HashMap中有兩個Entry的key相同,這與我們的想法相違背。


其實我覺得得平常大家沒有這種想法,都怪書本,書上經常都用String,而String的hashCode()和euqls()早就被重寫了