Core Java 談談HashMap

提及Java的HashMap相信你們都不是很陌生,可是對於HashMap內部結構有些同窗可能不太瞭解,我們下一步就將其展開。html

HashMap是基於Hash算法的,同理的還有HashSet和HashTable。個人一篇博文講述了若是將Object做爲Key的話,那麼就須要注意重寫其hashCode()和equals()方法,固然,這個equals()不是必要重寫的,可是在Effective Java中這條是一條準則。那麼針對這塊有個問題,若是隻寫了hashCode方法不寫equals或者反之呢? 我們一下子慢慢展開,相信同窗們讀完了會有所領悟。java

HashMap有幾個重要的概念 hashcode, entry, bucket,說道hashCode,其實它是一個Object對象的一個方法,也就說每一個方法都有一個,它的默認值是對象地址而後轉換爲int並返回。Entry是一個Map的靜態內部類,用於存儲Key, value,以及指向next的一個鏈表(linked list)。算法

    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

  Bucket是什麼呢,其實就是根據Hash值分佈的一個籃子,請參考以下圖表。app

下一步,我們直接進入HashMap源碼(java 7), 從它的最原始的空構造函數入手,初始化時長度爲16,0.75是負載因子。HashMap在初始化的時候沒有初始化這個Entry, 它是在put的時候進行的,看inflateTable(threadhold);填充表,在這個時候這個負載因子就用到了16*0.75=12,也就是bucket默認的長度爲12個,也就是hash的值只能存放12個,若是超過了12個的話,那就須要進行擴展,擴大兩倍好比32,而後再根據這個負載因子去計算。ide

那麼爲何用一個負載因子呢? 由於考慮到時間和空間之間的權衡才使用這個功能,若是這個負載因子小了,也就意味着分配的bucket比較少,這樣查詢和添加的時候速度比較快,可是增長bucket的速度變慢了。若是這個負載因子變大了,那麼每次分配bucket的數量也隨之增大了,這樣會佔據很大的內存空間。這個load factor參數決定了HashMap的性能。那是否還有其餘參數決定HashMap的性能呢,那就是初始化的的capacity(容量),這個也能夠初始化的時候設置,設置太大了在遍歷的時候也會消耗不少資源。函數

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    而後繼續看這個put,首先判斷key是否爲null,若是爲null的話,就會分配一個hashcode爲0的Entry用於存儲。也就是HashMap支持null值而且只容許一個性能

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

    /**
     * Inflates the table.
     */
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }
     * Retrieve object hash code and applies a supplemental hash function to the
     * result hash, which defends against poor quality hash functions.  This is
     * critical because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

  而後咱們在繼續看它的hash方法,它把key的hashCode取得了,而後又加工了一次,這是爲何呢?this

  由於爲了防止差的hashcode功能,也就是更好的提升了hashcode的計算。而後根據hashcode去找到bucket所在的index,若是index相同的話怎麼辦呢,而後再去取出它的key,將key和Entry裏邊的Key再次進行比較,若是相等equals()的話,那麼就能夠取出結果了,若是不相等的話,繼續Entry.next()進行判斷,最後若是沒有找到的話,就會返回nullspa

  這裏你們必定要注意Entry裏邊存放了key, value, next, 這時這個key取出來就用於判斷了。這下同窗們明白了若是hashcode相同了怎麼處理了吧。若是若是一個key在設計時只重寫了equals而沒有寫hashcode(),那麼會出現什麼現象呢? 答案是兩個對象會被當作兩個不一樣的對象放到了不一樣的bucket裏邊,這其實違背了一個很重要的原則:The part of the contract here which is important is: objects which are .equals() MUST have the same .hashCode(). 對象相等即它的hashCode相等。若是是在HashSet的裏面,會被當作兩個相同的對象,但這時錯誤的好比:Person a = new Person("zhangsang",13); Person b = new Person("zhangsan", 13); 若是隻重寫equals,那麼他們是equal的,可是放到set裏邊會被認爲兩個對象。設計

      還有一點就是put和get的時間複雜度爲O(1),這就是它的hash的原理了。下面是我本身寫的代碼,供你們參考。

package com.hqs.core;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Person {
    private final String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "name: " + this.name + " age: " + this.age + " ";
    }
    
    @Override
    public int hashCode() {
        return this.age;
    }
    
    @Override
    public boolean equals(Object obj) {
        boolean flag = false;
        if(obj == null)
            flag = false;
        if(obj == this)
            flag = true;
        if(getClass() != obj.getClass()) 
            flag = true;
        Person p = (Person) obj;
        if(p.name != null && this.name !=null) {
            if(p.name.equals(this.name) && p.age == this.age)
                flag = true;
        } else {
            flag = false;
        }
        return flag;    
    }
    
    
    public static void main(String[] args) {
        Person zhangsan = new Person("zhangsan", 13);
        Person lisi = new Person("lisi", 13);
        Person xiaoming = new Person("xiaoming", 12);
        
        System.out.println("zhangsan hashCode: " + zhangsan.hashCode());
        System.out.println("lisi hashCode: " + lisi.hashCode());
        System.out.println("xiaoming hashcode: " + xiaoming.hashCode());
        
        Map map = new HashMap();
        map.put(zhangsan, "first");
        map.put(lisi, "second");
        map.put(xiaoming, "third");
        
        Iterator<Person> it = map.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry<Person, String>)it.next();
            Person person= (Person)entry.getKey();
            String value = (String)entry.getValue();
            System.out.println(person + value);
        }
    }

}

zhangsan hashCode: 13
lisi hashCode: 13
xiaoming hashcode: 12
name: xiaoming age: 12 third
name: lisi age: 13 second
name: zhangsan age: 13 first

  若是有寫的不對的地方,還但願同窗們給一些意見:68344150@qq.com

相關文章
相關標籤/搜索