一篇文章让你真正读懂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()早就被重写了
 

猜你喜欢

转载自blog.csdn.net/CCSGTC/article/details/84348079