Java HashMap源码

137 阅读5分钟

HashMap源码

HashMap中的静态内部类Node

  • 实现了Map.Entry接口,所以视为一对元素/键值对为一个Entry对象
  • 四个成员变量
    • hash:通过键计算出来的哈希值
    • key:键
    • value:值
    • Node:记录下一个节点的地址值
static class Node<K, V> implements Map.Entry<K,y> {
    final int hash;
    fianl K key;
    V value;
    Node<K, V> next;
    
    Node(int hash, K kay, V vlaue, Node<K, V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    
    public final K getKey()         {return key;}
    public final V getValue()       {return value;}
    public final String toString()  {return key + "=" + value;}
    
    public final String toString() {return Object.hashCode(key) ^ Objects.hashCode(value);}
    
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
    
    public final boolean equals(Object o) {
        if (o == this) {return true;}
        
        return o instanceof Map.Entry<?, ?> e
            && Objects.equals(key, e.getKey())
            && Object.equals(values, e.getValue());
    }
}

HashMap中的成员变量

  • table:数组名,数组里面记录着Node对象
transient Node<K, V>[] table;

HashMap中的常量

  • DEFAULT_INITIAL_CAPACITY:默认初始容量 16
  • MAXIMUM_CAPACITY:最大容量 2的30次方
  • DEFAULT_LOAD_FACTOR:默认加载因子 0.75
static fainal int DEFAULT_INITIAL_CAPACITY = 1 << 4; //16
static final int MAXIMUM_CAPACITY = 1 << 30;
static fainal float DEFAULT_LOAD_FACTOR = 0.75f;

HashMap中的构造方法

  • 空参构造:将默认加载因子赋给成员变量loadFactor
    • 在此时底层的数组还是null
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

HashMap中的put方法

  • 底层继续调用putval方法
    • 传入的参数
      • 调用hash方法得出的被处理过的键的哈希值
      • 当前数据是否保留,false为不保留,即覆盖
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true)
}

HashMap中的hash方法

  • 计算传入键的哈希值并对其进行一些处理
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap中的putVal方法

  • 方法中的四个参数
    • hash:键的哈希值(经额外处理)
    • key:键
    • value:值
    • onlyIfAbsent:当前数据是否保留,false为不保留,即覆盖
  • 方法中定义的局部变量
    • tab:记录哈希表中数组的地址值
      • 好处:table存放在堆区,而方法在栈区运行,在栈区定义临时变量可以较少访问堆区的次数,提高效率
    • p:记录键值对对象
    • n:当前数组的长度
    • i:索引
  • 第一个if语句
    • 将哈希表中数组的地址值传递给tab
    • n记录数组长度
    • 如果 tab为null 或 n为0 执行if语句
      • 调用resize方法
        • 如果当前是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组
        • 如果不是第一次添加数据,会看数组中的元素是否达到了扩容条件
          • 如果没有达到扩容条件,底层不会有任何操作
          • 如果达到了扩容条件,底层就会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中
      • n重新记录数组的长度
  • 第二个if语句
    • 利用数组的长度和当前键的hash值计算出当前键值对对象在数组中应存入的位置,传递给i
    • 通过索引i获取数组中该位置的数据传递给p
    • 判断p是否为空
      • 若p为空
        • 调用newNode方法创建一个新的节点存入数组中
        • 第二个if语句执行完毕
      • 若p不为空,调用else语句
  • 第二个if语句中的else语句
    • 创建节点e
    • 创建存储键的k
    • 如果要添加的键的哈希值不等于p的键的哈希值
      • 如果p的类型是树的节点
        • 将p强转为树节点类型后调用putTreeVal方法添加节点,并将这个节点的地址赋给e,临时将这个节点命名为e
      • 如果p的类型不是树的节点,那么就是链表的节点,进入循环
        • 将p中存储的next节点赋给e
        • 如果e为null,执行if语句
          • 调用newNode方法创建新节点赋给p.next节点
          • 判断当前链表长度是否超过8,如果超过8,执行if语句
            • if语句中执行treeifyBin方法
              • 判断数组的长度是否大于等于64
              • 若同时满足这两个条件,就会把这个链表转成红黑树
          • break退出循环
        • 将e中的key传递给k
        • 如果 e中的hash等于要添加的键的hash 且 key值都相同
          • 不进行任何(添加)操作
          • break退出循环
        • 当e不为null且要添加的键值对对象不同时
          • 将e传递给p(意思就是把原来p的名字剥夺,将p这个名字交给它的下一个节点p.next)
          • 进入下一次循环
    • 如果要添加的键的哈希值等于p的键的哈希值,并且值也相同
      • 将p的地址值分享给e
    • 如果e(若是添加了新的元素,则为null,如果覆盖了原来的元素,则不为null,如果是重复元素,也不为null,这里不考虑在链表中间就遇到了完全相同的键值对,因为这种情况在上面就已经break掉了)不为null,这时后e中存储的地址指向被覆盖了的那个节点,执行if语句
      • 创建oldValue存储e中的value
      • 如果方法参数为覆盖老元素或oldValue为空(即原来的key对应得value为null)
        • 将e中的value覆盖为传递过来的value(这里更新的只是节点中的值,并没有更新节点)(无论两个value是否相同,都进行覆盖)
      • afterNodeAccess(e)这个方法和LinkedHashMap有关
      • 返回oldValue,结束方法
  • 操作次数++
  • size++
  • 若数组中的节点数量大于threshold(哈希表的扩容时机(数组长度 * 0.75)),执行if语句
    • 调用resize方法
  • afterNodeInsertion(evict)这个方法和LinkedHashMap有关
  • 返回null,表示当前没有覆盖任何元素,结束方法
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) {
                        treeifyBin(tab, hash);
                    }
                    break;
                }
            	if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                	break;
            	}
            	p = e;
        	}
    	}
    
    	if (e != null) {
        	V oldValue = e.value;
        	if (!onlyIfAbsent || oldValue == null) {
            	e.value = value;
        	}
        	afterNodeAccess(e);
        	return oldValue;
    	}
    }
    
    ++modCount;
    if (++size > threshold) {
        resize();
        afterNodeInsertion(evict);
        return null;
    }
}

HashMap中的newNode方法

  • 创建节点对象并返回
Node<K,N> newNode(int hash, K key, V value, Node<K, V>next) {
    return new Node<>(hash, key, value, next);
}