前言------
在使用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.youkuaiyun.com/CCSGTC/article/details/84344111
3.关于HashMap的一些基本操作:https://blog.youkuaiyun.com/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()早就被重写了