1. 程式人生 > 實用技巧 >每日收穫2

每日收穫2

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
        
        
    }
}