Java集合之HashMap源碼分析


一、HashMap簡介

  HashMap是基於哈希表的Map接口實現的,它存儲的是內容是鍵值對<key,value>映射。此類不保證映射的順序,假定哈希函數將元素適當的分布在各桶之間,可為基本操作(get和put)提供穩定的性能。

  ps:本文中的源碼來自jdk1.8.0_45/src。

1、重要參數

  HashMap的實例有兩個參數影響其性能。

  初始容量:哈希表中桶的數量

  加載因子:哈希表在其容量自動增加之前可以達到多滿的一種尺度

  當哈希表中條目數超出了當前容量*加載因子(其實就是HashMap的實際容量)時,則對該哈希表進行rehash操作,將哈希表擴充至兩倍的桶數。

  Java中默認初始容量為16,加載因子為0.75。

  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  static final float DEFAULT_LOAD_FACTOR = 0.75f;

2、HashMap的繼承關系

  HashMap實現了Coneable接口,能被克隆,實現了Serializable接口,因此它也支持序列化。

public class HashMap<K,V>
  extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

二、hashMap源碼分析

1、數據結構

  HashMap實際是一個鏈表的數組結構,當新建一個HashMap時,就會初始化一個數組。

     /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K,V>[] table;

  數組的類型為Node<K,V>,定義如下:

static class Node<K,V> implements Map.Entry<K,V>
    {
        final int hash;  
        final K key;
        V value;
        Node<K,V> next; //next指定鏈表中下一個實例
        Node(int hash, K key, V value, Node<K,V> next) 
        {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
      ....
    }

  可以看到,Node就是數組中的元素,而每一個Map.Node就是一個key-value對,它還持有指向下一個元素的引用,這樣就構成了鏈表。簡單的構造圖如下所示:

  

2、構造函數

  HashMap有四個構造函數,代碼如下:

     //1.構造一個帶指定初始容量和加載因子的空HashMap
    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);
    }
    //2.構造一個帶指定初始容量和默認加載因子(0.75)的空 HashMap
    public HashMap(int initialCapacity)
    {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
     //3.構造一個具有默認初始容量 (16)和默認加載因子 (0.75)的空 HashMap
    public HashMap() 
    {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
     //4.構造一個映射關系與指定 Map相同的新 HashMap
    public HashMap(Map<? extends K, ? extends V> m)
    {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

3、HashMap常用方法

  void clear():從此映射中移除所有映射關系

 public void clear()  //清空HashMap,將數組所有元素置為null
    {
        Node<K,V>[] tab;
        modCount++;
        if ((tab = table) != null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

  Object clone():返回此 HashMap 實例的淺表副本

   public Object clone()
    {
        HashMap<K,V> result;
        try 
        {
            result = (HashMap<K,V>)super.clone(); //調用父類clone方法
        } 
        catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
        result.reinitialize();  //初始化
        result.putMapEntries(this, false);  //將此HashMap元素放入result中
        return result;
    }

  boolean containsKey(Object key):如果此映射包含對於指定鍵的映射關系,則返回 true。

  public boolean containsKey(Object key)
    {
        return getNode(hash(key), key) != null;
    }
    final Node<K,V> getNode(int hash, Object key)  //根據hash值和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) 
        {
            if (first.hash == hash && // 查找第一個節點
                ((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;
    }

  boolean containsValue(Object value):如果此映射將一個或多個鍵映射到指定值,則返回 true。

  public boolean containsValue(Object value)
    {
        Node<K,V>[] tab; V v;
        if ((tab = table) != null && size > 0) 
        {
            for (int i = 0; i < tab.length; ++i)  //外層循環搜索數組
            {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)  //內層循環搜索鏈表
                {
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }

  Set<Map.Entry<K,V>> entrySet():返回此映射所包含的映射關系的Set視圖,即返回鍵值對的集

  public Set<Map.Entry<K,V>> entrySet()
    {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

  get(Object key):返回指定鍵所映射的值;如果對於該鍵來說,此映射不包含任何映射關系,則返回 null

 public V get(Object key) 
    {
        Node<K,V> e;
        //通過getNode方法先找到包含該key的節點,再返回節點的value
        return (e = getNode(hash(key), key)) == null ? null : e.value; 
    }

  boolean isEmpty():如果此映射不包含鍵-值映射關系,則返回 true

 public boolean isEmpty() 
    {
        return size == 0;
    }

  Set<K> keySet():返回此映射中所包含的鍵的set視圖,即返回鍵集

public Set<K> keySet() 
    {
        Set<K> ks;
        return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
    }

  V put(K key,V value):在此映射中關聯指定值與指定鍵。

  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)//該key的hash值對應的那個節點為空
            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)  //若該key對應的鍵值對已經存在,則用新的value取代舊的value
            {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
       // 若“該key”對應的鍵值對不存在,則將“key-value”添加到table中 
        ++modCount;
        //如果加入該鍵值對后超過最大閥值,則進行resize操作
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

  注意:在JDK1.8中,HashMap的存儲結構采用數組+鏈表+紅黑樹這種組合型數據結構。當hash值發生沖突時,會采用鏈表或者紅黑樹解決沖突,當同一hash值得節點數小於8時,則采用鏈表,否則采用紅黑樹。這一改變,主要是提高查詢速度。

  void putAll(Map<? extends K, ? extends V> m):將指定映射的所有映射關系復制到此映射中,這些映射關系將替換此映射目前針對指定映射中所有鍵的所有映射關系

  public void putAll(Map<? extends K, ? extends V> m)
    {
        putMapEntries(m, true);
    }

  V remove(Object key):從此映射中移除指定鍵的映射關系(如果存在)

   public V remove(Object key) 
    {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

   int size():返回此映射中的鍵-值映射關系數

   public int size() 
{
return size; }

  Collection<V> values():返回此映射所包含的值的Collection視圖

 //返回“value集合”,實際上返回的是一個Values對象
    public Collection<V> values()
    {
        Collection<V> vs;
        return (vs = values) == null ? (values = new Values()) : vs;
    }

三、HashMap的應用示例代碼

public class HashMapDemo
{
    public static void main(String[] args)
    {
        HashMap<String, String> hm = new HashMap<>();
        System.out.println("調用put函數:");
        hm.put("01", "Amy");
        hm.put("02", "harry");
        hm.put("03", "Gary");
        hm.put("04", "Amy");
        HashMap<String, String> hm2=(HashMap<String, String>) hm.clone();
        System.out.println(hm2);
        System.out.println("調用clear函數:");
        hm2.clear();
        System.out.println(hm2);
        System.out.println("調用containsKey函數:"+hm.containsKey("01")); 
        System.out.println("調用containsValue函數:"+hm.containsValue("Amy"));
        System.out.println("調用entrySet函數:");
        Set<Entry<String, String>> result=hm.entrySet();
        for(Entry<String,String> entry:result)
        {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }
        
        System.out.println("調用get函數:"+hm.get("03"));
        
        System.out.println("調用isEmpty函數:"+hm.isEmpty());
        System.out.println("調用keySet函數:");
        Set<String> kr=hm.keySet();
        //打印所有的key值
        for(String str:kr)
        {
            System.out.println(str);
        }
        System.out.println("調用remove函數:");
        hm.remove("02");
        System.out.println(hm);
        System.out.println("調用size函數:"+"size="+hm.size());
        System.out.println("調用values函數:");
        Collection<String> vs=hm.values();
        for(String str:vs)
        {
            System.out.println(str);
        }    
    }
}

  運行結果截圖如下:

四、HashMap總結

  HashMap是一個很有用的集合框架,通過下述三個方法可以分別得到HashMap的鍵集、值集和鍵值對集。

  鍵集:Set<K> keySet()

  值集:Collection<K> values()  

  鍵值對集:Set<Map.Entry<K,V>> entrySet()

  以上就是我對於HashMap的總結,由於水平有限,本文並未對HashMap的實現機制做深層次的解析,還在進一步的學習中,共勉!

 

Java集合系列:

       Java集合系列之LinkedList源碼分析

   Java集合系列之ArrayList源碼分析

 


免責聲明!

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



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