java基礎解析系列(三)---HashMap


java基礎解析系列(三)---HashMap

java基礎解析系列

基本概念

  • 節點: Node<Key,Value>,存放key和value
static class Node<K,V> implements Map.Entry<K,V> {    
    final int hash;   
    final K key;   
    V value;   
    Node<K,V> next;
}
  • 鍵值對數組:Node<K,V>[] table
  • 加載因子
  • 容量 :Node數組的長度
  • 大小:hashmap存放的Node的數目
  • 閾值:容量*加載因子

工作原理

  • 創建一個長度為2的次冪的node數組
  • put的時候,計算key的hash值,將hash值與長度-1進行與運算
  • 如果數組該下標的位置為空,直接存放,如果不為空,判斷節點是否為樹節點,如果是的話按紅黑樹的方式存入,否則按照鏈表的形式存入
  • 當hashmap的節點數目大於閾值的時候,將會重新構造hashmap,而這種操作是費時的操作,所以建議初始化一個合適的容量

  • 默認容量,2的四次方

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

  • 默認加載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • node 數組
 transient Node<K,V>[] table;

    
  • 鍵值對數目,不是table的長度
    /**
     * The number of key-value mappings contained in this map.
     */
    
    transient int size;
  • 閾值
    /**
     * The next size value at which to resize (capacity * load factor).
     *
     * @serial
     */
    //閾值
    int threshold;
  • 加載因子

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
     //加載因子
    final float loadFactor;

構造方法

  • 傳入初始容量和加載因子
 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
  • 傳入初始容量,使用默認的加載因子

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
  • 無參數,默認容量和加載因子
    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

  • 容量必須是2的n次方,當你傳入的參數不符合條件,會有方法找到一個大於這個參數的最小的2的n次方數(比如大於6的最小2的n次冪是8),

put方法

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

  • 直接用偽代碼表示


put()
{
    index=[hash(key)&(captity-1)]----下標的最大值為captity-1,進行與運算后最終的結果小於等於最大下標
    if(table[index])==null)
        直接添加node
    else
    {
        if(p是treenode)
        {
            直接將節點添加到紅黑樹    
        }
        else
        {
            如果不是紅黑樹是鏈表
            if(p的鍵值==key)
                覆蓋value
            else
            {
                遍歷鏈表:
                {
                    if(有對應的key)
                    {
                        覆蓋value
                        break;
                    }
                }
               遍歷完成后沒有發現對應的key
                {
                    添加到鏈表
                    if(鏈表長度>8)
                    {
                        將鏈表轉化為紅黑樹
                    }
                }
            }
        }
        if(大小大於閾值)
        {
            容量加倍,重新構造
        }
        
    }
}

get方法

 public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            //如果鏈表的第一個節點是的鍵和要查找的鍵相等,那么返回該node
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //如果不是的,看該節點是不是樹節點,是的話,用樹的方法查找節點,如果不是的按鏈表的方式查找
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

為什么長度設置為2的n次方

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
  • 存放node到table數組的時候,他的下標是通過(n-1)&hash計算出來的(數組長度-1 和 key的hash的值相與,最后結果小於等於長度-1),n為table的長度。
  • 當長度為2的n次冪的時候,(n-1)&hash==hash%n,而前者是位運算,速度會快很多

負載因子

  • 負載因子較大,說明閾值較大,也就意味着可能發生更多的沖突
  • 負載因子較小,說明閾值較小,也就意味着可能會更少的沖突
  • 發生沖突的時候,會降低hashmap的查找速度,所以當要求更少的內存的時候可以增加負載因子,當要求更高的查找速度的時候,可以減少負載因子。
  • 默認的參數是平衡的選擇,所以不建議修改

我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)

作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM