Java HashMap LinkedHashMap 區別及原理

HashMap原理
HashMap是Map的一個常用的子類實現。其實使用散列算法實現的。
HashMap內部維護着一個散列數組(就是一個存放元素的數組),我們稱其爲散列桶,而當我們向HashMap中存入一組鍵值對時,HashMap首先獲取key這個對象的hashcode()方法的返回值,然後使用該值進行一個散列算法,得出一個數字,這個數字就是這組鍵值對要存入散列數組中的下標位置。
那麼得知了下標位置後,HashMap還會查看散列數組當前位置是否包含該元素。(這裏要注意的是,散列數組中每個元素並非是直接存儲鍵值對的,而是存入了一個鏈表,這個鏈表中的每個節點纔是真實保存這組鍵值對的。)檢查是否包含該元素時根據當前要存入的key在當前散列數組對應位置中的鏈表裏是否已經包含這個key,若不包含則將這組鍵值對存入鏈表,否則就替換value。
那麼在獲取元素時,HashMap同樣先根據key的hashcode值進行散列算法,找到它在散列數組中的位置,然後遍歷該位置的鏈表,找到該key所對應的value之後返回。
看到這裏可能有個疑問,鏈表中應該只能存入一個元素,那麼HashMap是如何將key-value存入鏈表的某個節點的呢?實際上,HashMap會將每組鍵值對封裝爲一個Entry的實例,然後將該實例存入鏈表。
如圖所示:
這裏寫圖片描述

HashMap的存取是依賴於key的hashcode方法的返回值的,而hashcode方法實際上是在Object中定義的。其定義如下:
int hashCode()
代碼如下:

public class HashMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> hashMap = new HashMap<String,Integer>();
        hashMap.put("one", 1);
        hashMap.put("two", 2);
        hashMap.put("three", 3);
        hashMap.put("four", 4);
        hashMap.put("five", 5); 
        hashMap.put("six", null);

        //遍歷Map中的key的hashMap
        for(String str : hashMap.keySet()) {
            System.out.println(str +":"+ str.hashCode() );
        }

    }
}

運行結果:
six:113890
four:3149094
one:110182
two:115276
three:110339486
five:3143346

遍歷每一組鍵值對
Set

public class HashMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> hashMap = new HashMap<String,Integer>();
        hashMap.put("one", 1);
        hashMap.put("two", 2);
        hashMap.put("three", 3);
        hashMap.put("four", 4);
        hashMap.put("five", 5); 
        hashMap.put("six", null);

        //遍歷鍵值對
        Set<Entry<String, Integer>> set = hashMap.entrySet();
        for(Entry<String, Integer> e : set) {
            System.out.print("key:"+e.getKey()+"\t");
            System.out.println("value:"+e.getValue());
        }
    }
}

運行結果:
key:six value:null
key:four value:4
key:one value:1
key:two value:2
key:three value:3
key:five value:5

重寫一個類的hashcode()方法有以下注意事項:
1、若一個類重寫了equals方法,那麼就應當重寫hashcode()方法。
2、若兩個對象的equals方法比較爲true,那麼它們應當具有相同的hashcode值。
3、對於同一個對象而言,在內容沒有發生改變的情況下,多次調用hashCode()方法應當總是返回相同的值。
4、對於兩個對象equals比較爲false的,並不要求其hashcode值一定不同,但是應儘量保證不同,這樣可以提高散列表性能。

代碼如下:

public class HashMapDemo {
    public static void main(String[] args) {
        Map<Card, Person> map = new HashMap<Card, Person>();
        Person p1 = new Person(new Card("001"),"張三");  
        Person p2 = new Person(new Card("002"),"李四");  

        map.put(p1.getCard(), p1);  
        map.put(p2.getCard(), p2);  

        System.out.println("HashMap 中存放的人員信息:\n"+map);  

        //張三改名爲張山,身份證號不變。  
        Person p3 = new Person(new Card("001"),"張山");  
        map.put(p3.getCard(), p3);  

        System.out.println("張三改名爲張山後 HashMap 中存放的人員信息:\n"+map);  

        //查找身份證爲001 的人員信息  
        System.out.println("查找身份證爲:001 的人員信息:"+map.get(new Card("001")));
    }
}

class Card {
    private final String IdCard;

    public Card(String idCard) {
        super();
        IdCard = idCard;
    }

    public String getIdCard() {
        return IdCard;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((IdCard == null) ? 0 : IdCard.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        // 如果地址一樣,則兩個對象相同 
        if (this == obj) {
            return true;
        }
        // 如果兩個對象是同一類型,則比較其屬性值是否都相同
        //如果都相同,則說明兩個對象也相同;
        //否則,說明這兩個對象不相同。
        if(obj instanceof Card) {
            Card card = (Card)obj;
            return card.IdCard .equals(this.IdCard);
        }
        return false;
    }

    @Override
    public String toString() {
        return "Card [IdCard=" + IdCard + "]";
    }

}


class Person {
    private Card card;
    private String name;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((card == null) ? 0 : card.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    /** 
     * 重寫equals()方法 當兩個人得身份證號相同以及姓名相同時,表示這兩個人是同一個人。 
     */  
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {  
            return true;  
        }  
        if (obj instanceof Person) {  
            Person p = (Person) obj;  
            return this.card.equals(p.card) && this.name.equals(p.name);  
        }  
        return false;
    }

    public Person(Card card, String name) {
        super();
        this.card = card;
        this.name = name;
    }

    public Card getCard() {
        return card;
    }
    public void setCard(Card card) {
        this.card = card;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + "]";
    }

}

運行結果:
HashMap 中存放的人員信息:
{Card [IdCard=001]=Person [name=張三], Card [IdCard=002]=Person [name=李四]}
張三改名爲張山後 HashMap 中存放的人員信息:
{Card [IdCard=001]=Person [name=張山], Card [IdCard=002]=Person [name=李四]}
查找身份證爲:001 的人員信息:Person [name=張山]

裝載因子及HashMap優化

在散列表中有一下名詞需要了解:
Capacity:容量, hash表裏bucket(桶)的數量, 也就是散列數組大小.
Initial capacity:初始容量, 創建hash表的時 初始bucket的數量, 默認構建容量是16. 也可以使用特定容量.
Size : 大小, 當前散列表中存儲數據的數量.
Load factor:加載因子, 默認值0.75(就是75%), 向散列表增加數據時如果 size/capacity 的值大於Load factor則發生擴容並且重新散列(rehash).
那麼當加載因子較小時候散列查找性能會提高, 同時也浪費了散列桶空間容量. 0.75是性能和空間相對平衡結果. 在創建散列表時候指定合理容量, 從而可以減少rehash提高性能。
例如:

Map<String, Integer> map = new HashMap<String, Integer>(10000);

存入大量數據時可以使用該方法,減少擴容次數,提高性能
如果數據量很小,不推薦使用此方法,會造成空間浪費,用默認無參的即可

LinkedHashMap實現有序的Map

Map 接口的哈希表和鏈表實現,具有可預知的迭代順序。此實現與 HashMap 的不同之處在於,LinkedHashMap維護着一個雙向循環鏈表。此鏈表定義了迭代順序,該迭代順序通常就是將存放元素的順序。
需要注意的是,如果在Map中重新存入以有的key,那麼key的位置會不會發生改變,只是將value值替換。

總結:
HashMap和LinkedHashMap區別在於HashMap是無序的,LinkedHashMap是有序的。在使用HashMap時,要根據實際情況選擇最優的解決方案,降低性能和空間上的浪費。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章