源码剖析之HashMap底层实现原理

171 阅读3分钟

基本原理

Map接口的基于哈希表的实现。

HashMap的实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的桶数,初始容量只是哈希表创建时的容量。负载因子是哈希表在其容量自动增加之前允许达到的程度的度量。当哈希表中的条目数超过负载因子和当前容量的乘积时,对哈希表进行重新哈希(即重建内部数据结构),使哈希表具有大约两倍的桶数。

作为一般规则,默认负载因子 (.75) 在时间和空间成本之间提供了良好的折衷。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put )。在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以尽量减少重新哈希操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生重新哈希操作。

核心知识点

1. 初始容量

容量是哈希表中的桶数,初始容量只是哈希表创建时的容量。

//默认初始容量 - 必须是 2 的幂 
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16`

2. 最大容量

//最大容量,如果一个更高的值由任何一个带参数的构造函数隐式指定时使用。 必须是 2 <= 1<<30 的幂。 
static final int MAXIMUM_CAPACITY = 1 << 30;

3. 负载因子

//负载因子是哈希表在其容量自动增加之前允许达到的程度的度量。
static final float DEFAULT_LOAD_FACTOR = 0.75f;

4. 数据结构

数组+链表

5. 扩容机制

当哈希表中的条目数超过负载因子和当前容量的乘积时,对哈希表进行重新哈希(即重建内部数据结构),使哈希表具有大约两倍的桶数。

6. 构造函数

/** *构造一个具有指定初始容量和负载因子的空HashMap 。 
    参数: 
    initialCapacity - 
    初始容量 loadFactor – 负载因子 
    抛出: IllegalArgumentException – 
    如果初始容量为负或负载因子为非 
    **/ 
    public HashMap(int initialCapacity, float loadFactor) { 
       if (initialCapacity < 0) //判断传入的容量
        throw new IllegalArgumentException("Illegal initial capacity: " +     initialCapacity); 
        
    //出入的容量不能超过最大容量30,如果超过则设置位最大容量
    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);
    }

7. put元素

计算hash值

public V put(K key, V value) { 
   return putVal(hash(key), key, value, false, true);
   } 
   //对key进行16进制的位运算获取hash值 
    static final int hash(Object key) { 
     int h;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
     }

添加元素

/** 
 *hash 元素key的hash值
 *key 元素key 
 *value – 要放置的值 
 *onlyIfAbsent – 如果为真,则不更改现有值 
 *evict – 如果为 false,则表处于创建模式。 
 */ 
 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)
 //如果当前map为空则定义默认容量16 
 n = (tab = resize()).length; 
 if ((p = tab[i = (n - 1) & hash]) == null)
   //如果根据hash计算的下标,查找数组中的元素不存在则创建一个node放置元素 
   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; }
                   //如果已经存在相同的key则覆盖 
                     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; //如果当前链表节点超过8个,则进行扩容 
                 if (++size > threshold) 
                 resize(); 
                 afterNodeInsertion(evict); 
                 return null;
            }

8. 指针碰撞

如果当前元素的hash值位运算后所在的位置已经有元素,则将元素打包成node节点挂在当前节点后面

for (int binCount = 0; ; ++binCount) {
   if ((e = p.next) == null) { 
   //如果当前节点的下一个节点为null,则将插入的元素打包成newNode插入当前节点后面
      p.next = newNode(hash, key, value, null); 
        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 
           treeifyBin(tab, hash); break;
         } 
     }

9. 查找原理

//如果通过key能获取到value则返回,否则返回null 
public V get(Object key) { 
   Node<K,V> e; 
   return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/** 
* Implements Map.get and related methods 
* @param hash hash for key 
* @param key the key 
* @return the node, or null if none 
*/ 
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数组中的第一个node的hash相同则返回, 
      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 {
                   //在do-while循环中获取hash值和key值相同的node节点返回 
                     if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                       return e;  
                     } while ((e = e.next) != null); 
                    } 
                  } 
                  return null; 
                }

本次分析就结束了,收工! "本文正在参加「金石计划」"