一篇文章讓你真正讀懂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()早就被重寫了