每日收穫2
阿新 • • 發佈:2020-08-06
HashMap集合:
1 HashMap集合底層是雜湊表/散列表的資料結構。
2 雜湊表是一個怎樣的資料結構呢?
雜湊表是一個數組和單向連結串列的結合體。
陣列:在查詢方面效率很高,隨機增刪方面效率較低。
單向連結串列:在隨機增刪方面效率較高,在查詢方面效率較低。
雜湊表將以上的兩種資料結構融合在一起,充分發揮他們各自的優點。
3 HashMap底層的原始碼:
public class HashMap{
// HashMap底層實際上就是一個數組。(一維陣列)
transient Node<K,V>[] table;
// 靜態的內部類HashMap.Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // 雜湊值(雜湊值是key的hashCode()方法的執行結果。hash值通過雜湊函式/演算法,可以轉換儲存為陣列的下標。)
final K key; // 存到Map集合中的那個key
V value; // 儲存到Map集合中的那個value
Node<K,V> next; // 下一個節點的記憶體地址
}
雜湊表/散列表:一維陣列,這個陣列中每一個元素是一個單向連結串列。(陣列和連結串列的結合體)
4 最主要掌握的是:
map.put(k,v)
v = map.get(k)
以上這兩個方法的實現原理,是必須掌握的。
5 HashMap集合的key部分特點:
無序,不可重複。
為什麼無序?因為不一定掛到哪個單向連結串列上。
不可重複是怎麼保證的?equals方法來保證HashMap集合的key不可重複。
如果key重複了,value會覆蓋。
放在HashMap集合key部分的元素其實就是放到了HashSet集合中了。
所以HashMap集合中的元素也需要同時重寫hashCode()+equals()方法。
6 雜湊表HashMap使用不當時無法發揮性能!
假設將所有的hashCode()方法返回值固定為某個值,那麼會導致底層雜湊表變成了純單向連結串列。
這種情況我們稱為:雜湊分佈不均勻
什麼是雜湊分佈均勻?
假設有100個元素,10個單向連結串列,那麼每個單向連結串列上有10個節點,這是最好的。是雜湊分佈均勻的。
假設將所有的hashCode()方法返回值都設定為不一樣的值,可以嗎?有什麼問題?
不行,因為這樣的話導致底層雜湊表就成為了一維陣列了,沒有連結串列的概念了。
也是雜湊分佈不均勻。
雜湊分佈均勻需要你重寫hashCode()方法時有一定的技巧。
7 重點:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同時重寫hashCode和equals方法。
8 HashMap集合的預設初始化容量是16,預設載入因子是0.75
這個預設載入因子是當HashMap底層陣列的容量達到75%的時候,陣列開始擴容。
重點,記住:HashMap集合初始化容量必須是2的倍數,這也是官方推薦的,這是因為達到單列分佈均勻,為了提高HashMap集合的存取效率,所必需的。
9 HashMap在初始化的過程中
1 向Map集合中存,以及從Map集合中取,都是先呼叫key的hashCode方法,然後在呼叫equals方法。
equals方法有可能呼叫,也有可能不呼叫。
拿put(k,v)方法舉例,什麼時候equals不會呼叫?
k.hashCode()方法返回雜湊值,
雜湊值經過雜湊演算法轉成陣列下標,
陣列下標位置上如果是null,equals不需要執行。
拿get(k),什麼時候equals不會呼叫?
k.hashCode()方法返回雜湊值,
雜湊值經過雜湊演算法轉成陣列下標,
陣列下標位置上如果是null,equals不需要執行。
2 注意:如果一個類的equals方法重寫了,那麼hashCode()方法必須重寫。
並且equals方法返回的如果是true,hashCode()方法返回的值必須一樣的。
equals方法返回true表示兩個物件相同,在同一個單向連結串列上比較。
那麼對於同一個單向連結串列上的節點來說,他們的雜湊值都是相同的。
所以hashCode()方法的返回值也應該相同。
3 hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是這兩個方法需要同時生成。
4 終極結論:
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同時重寫hashCode方法和equals方法。
HashMap記憶體圖:
案例:
package com.javaSe.Map; import java.util.HashMap; import java.util.Map; import java.util.Set; /* HashMap集合: 1 HashMap集合底層是雜湊表/散列表的資料結構。 2 雜湊表是一個怎樣的資料結構呢? 雜湊表是一個數組和單向連結串列的結合體。 陣列:在查詢方面效率很高,隨機增刪方面效率較低。 單向連結串列:在隨機增刪方面效率較高,在查詢方面效率較低。 雜湊表將以上的兩種資料結構融合在一起,充分發揮他們各自的優點。 3 HashMap底層的原始碼: public class HashMap{ // HashMap底層實際上就是一個數組。(一維陣列) transient Node<K,V>[] table; // 靜態的內部類HashMap.Node static class Node<K,V> implements Map.Entry<K,V> { final int hash; // 雜湊值(雜湊值是key的hashCode()方法的執行結果。hash值通過雜湊函式/演算法,可以轉換儲存為陣列的下標。) final K key; // 存到Map集合中的那個key V value; // 儲存到Map集合中的那個value Node<K,V> next; // 下一個節點的記憶體地址 } 雜湊表/散列表:一維陣列,這個陣列中每一個元素是一個單向連結串列。(陣列和連結串列的結合體) 4 最主要掌握的是: map.put(k,v) v = map.get(k) 以上這兩個方法的實現原理,是必須掌握的。 5 HashMap集合的key部分特點: 無序,不可重複。 為什麼無序?因為不一定掛到哪個單向連結串列上。 不可重複是怎麼保證的?equals方法來保證HashMap集合的key不可重複。 如果key重複了,value會覆蓋。 放在HashMap集合key部分的元素其實就是放到了HashSet集合中了。 所以HashMap集合中的元素也需要同時重寫hashCode()+equals()方法。 6 雜湊表HashMap使用不當時無法發揮性能! 假設將所有的hashCode()方法返回值固定為某個值,那麼會導致底層雜湊表變成了純單向連結串列。 這種情況我們稱為:雜湊分佈不均勻 什麼是雜湊分佈均勻? 假設有100個元素,10個單向連結串列,那麼每個單向連結串列上有10個節點,這是最好的。是雜湊分佈均勻的。 假設將所有的hashCode()方法返回值都設定為不一樣的值,可以嗎?有什麼問題? 不行,因為這樣的話導致底層雜湊表就成為了一維陣列了,沒有連結串列的概念了。 也是雜湊分佈不均勻。 雜湊分佈均勻需要你重寫hashCode()方法時有一定的技巧。 7 重點:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同時重寫hashCode和equals方法。 8 HashMap集合的預設初始化容量是16,預設載入因子是0.75 這個預設載入因子是當HashMap底層陣列的容量達到75%的時候,陣列開始擴容。 重點,記住:HashMap集合初始化容量必須是2的倍數,這也是官方推薦的,這是因為達到單列分佈均勻,為了提高HashMap集合的存取效率,所必需的。 9 HashMap在初始化的過程中*/ public class HashMapTest01 { public static void main(String[] args) { // 測試HashMap集合key部分的元素特點 // Integer是key,他的hashCode和equals都重寫了。 // String的hashCode和equals也都重寫了。 Map<Integer,String> map = new HashMap<>(); map.put(1111,"zhangsan"); map.put(2222,"lisi"); map.put(3333,"wangwu"); map.put(4444,"liuliu"); map.put(4444,"king");// key重複的時候value會自動覆蓋。 System.out.println(map.size()); //4 Set<Map.Entry<Integer,String>> set = map.entrySet(); for(Map.Entry<Integer,String> entry :set){// 驗證結果:HashMap集合key部分元素:無序不可重複。 System.out.println(entry.getKey() + "=" + entry.getValue()); } } }
案例2:
package com.javaSe.Map; import com.javaSe.Map.bean.Student; import java.util.HashSet; import java.util.Set; /* 1 向Map集合中存,以及從Map集合中取,都是先呼叫key的hashCode方法,然後在呼叫equals方法。 equals方法有可能呼叫,也有可能不呼叫。 拿put(k,v)方法舉例,什麼時候equals不會呼叫? k.hashCode()方法返回雜湊值, 雜湊值經過雜湊演算法轉成陣列下標, 陣列下標位置上如果是null,equals不需要執行。 拿get(k),什麼時候equals不會呼叫? k.hashCode()方法返回雜湊值, 雜湊值經過雜湊演算法轉成陣列下標, 陣列下標位置上如果是null,equals不需要執行。 2 注意:如果一個類的equals方法重寫了,那麼hashCode()方法必須重寫。 並且equals方法返回的如果是true,hashCode()方法返回的值必須一樣的。 equals方法返回true表示兩個物件相同,在同一個單向連結串列上比較。 那麼對於同一個單向連結串列上的節點來說,他們的雜湊值都是相同的。 所以hashCode()方法的返回值也應該相同。 3 hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是這兩個方法需要同時生成。 4 終極結論: 放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同時重寫hashCode方法和equals方法。 */ public class HashMapTest02 { public static void main(String[] args) { Student s1 = new Student("zhangsan"); Student s2 = new Student("zhangsan"); // 重寫equals方法之前是false // System.out.println(s1.equals(s2)); // 重寫equals方法之後是true System.out.println(s1.equals(s2)); // true (s1 和 s2 表示相等) System.out.println("s1的hashCode = " + s1.hashCode()); //284720968 重寫hashCode之後 值變成了-1432604525 System.out.println("s2的hashCode = " + s2.hashCode()); //122883338 重寫hashCode之後 值變成了-1432604525 // s1.equals(s2)結果已經是true了,表示s1和s2是一樣的,相同的,那麼往HashSet集合中放的話,按說只能放進去1個。(HashSet集合特點:無序不可能重複。) Set<Student> studentSet = new HashSet<>(); studentSet.add(s1); studentSet.add(s2); System.out.println(studentSet.size()); // 這個結果按說應該是1,但是結果是2,顯然不符合HashSet儲存特點。怎麼辦? } }
Product物件:
package com.javaSe.Map.bean; import java.util.Objects; public class Product { private int no; private String name; public Product() { } public Product(int no, String name) { this.no = no; this.name = name; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } // 重寫hashCode + equals // 假設業務要求:商品編號和商品名字都相同時,表示同一件商品。 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Product product = (Product) o; return no == product.no && Objects.equals(name, product.name); } @Override public int hashCode() { return Objects.hash(no, name); } }
Student物件:
package com.javaSe.Map.bean; import java.util.Objects; public class Student { private String name; public Student() { } public Student(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } // hashCode // equals(如果名字一樣,表示是同一個學生。) /*public boolean equals(Object o) { if(null == o && !(o instanceof Student)) return false; if(o == this) return true; Student s = (Student)o; return this.name.equals(s.name); }*/ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return name.equals(student.name); } public int hashCode() { return Objects.hash(name); } }案例:
package com.javaSe.Map; import com.javaSe.Map.bean.Student; import java.util.HashSet; import java.util.Set; /* 1 向Map集合中存,以及從Map集合中取,都是先呼叫key的hashCode方法,然後在呼叫equals方法。 equals方法有可能呼叫,也有可能不呼叫。 拿put(k,v)方法舉例,什麼時候equals不會呼叫? k.hashCode()方法返回雜湊值, 雜湊值經過雜湊演算法轉成陣列下標, 陣列下標位置上如果是null,equals不需要執行。 拿get(k),什麼時候equals不會呼叫? k.hashCode()方法返回雜湊值, 雜湊值經過雜湊演算法轉成陣列下標, 陣列下標位置上如果是null,equals不需要執行。 2 注意:如果一個類的equals方法重寫了,那麼hashCode()方法必須重寫。 並且equals方法返回的如果是true,hashCode()方法返回的值必須一樣的。 equals方法返回true表示兩個物件相同,在同一個單向連結串列上比較。 那麼對於同一個單向連結串列上的節點來說,他們的雜湊值都是相同的。 所以hashCode()方法的返回值也應該相同。 3 hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是這兩個方法需要同時生成。 4 終極結論: 放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同時重寫hashCode方法和equals方法。 5 對於雜湊表資料結構來說: 如果o1和o2的hash值相同,一定是放到同一個單向連結串列上。 當然如果o1和o2的hash值不同,但由於雜湊演算法執行結束之後轉換的陣列下標可能相同,此時會發生“雜湊碰撞”。 */ public class HashMapTest02 { public static void main(String[] args) { Student s1 = new Student("zhangsan"); Student s2 = new Student("zhangsan"); // 重寫equals方法之前是false // System.out.println(s1.equals(s2)); // 重寫equals方法之後是true System.out.println(s1.equals(s2)); // true (s1 和 s2 表示相等) System.out.println("s1的hashCode = " + s1.hashCode()); //284720968 重寫hashCode之後 值變成了-1432604525 System.out.println("s2的hashCode = " + s2.hashCode()); //122883338 重寫hashCode之後 值變成了-1432604525 // s1.equals(s2)結果已經是true了,表示s1和s2是一樣的,相同的,那麼往HashSet集合中放的話,按說只能放進去1個。(HashSet集合特點:無序不可能重複。) Set<Student> studentSet = new HashSet<>(); studentSet.add(s1); studentSet.add(s2); System.out.println(studentSet.size()); // 這個結果按說應該是1,但是結果是2,顯然不符合HashSet儲存特點。怎麼辦? } }
案例:
package com.javaSe.Map; import java.util.HashMap; import java.util.Map; /* HashMap集合key部分允許為null嗎? 允許 但是要注意:HashMap集合的key null值只能有一個。 有可能面試的時候會遇到這個問題。 */ public class HashMapTest03 { public static void main(String[] args) { Map<Integer,String> map = new HashMap<>(); // HashMap集合允許key為null map.put(null,null); System.out.println(map.size()); // 1 // key重複的話value是覆蓋! map.put(null,"100"); System.out.println(map.size()); // 1 // 通過key獲取value嗎? System.out.println(map.get(null)); // 100 } }