HashMap底层原理

主要jdk1.8中

主要数据结构为:数组+链表+红黑树

构造HashMap后,table一开始为空,默认loadfactor=0.75,

size;//键值对个数

DEFAULT_INITIAL_CAPACITY = 1 << 4;
DEFAULT_LOAD_FACTOR = 0.75f;//扩容阈值,初始化默认0.75
TREEIFY_THRESHOLD = 8;
UNTREEIFY_THRESHOLD = 6;
MIN_TREEIFY_CAPACITY = 64;

transient Node<K,V>[] table;//Hashmap中数组槽
transient int size;//键值对元素个数


put方法

putval()–resize()

  1. 最开始table数组为空,去resize()–>指定默认数组容量–默认加载因子
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;

2.**封装好new Node(hash,key,value,next)**再放入到数组中

计算数组下标**(n-1)&hash** 此下标当前位置没有值,则把这个新节点放进去

若此数组下标处已经有元素

hashmap插入流程

    1. 数组为空,初始化数组,指定容量为16,loadfactor=0.75
    1. 将key --value封装成Node内部类,Node是单向链表
    1. 计算hash,通过hash去计算数组下标
    1. 当前数组下标中没有元素,直接放到该下标处

      4.1当前数组下标中如果有元素,判断该元素与传入元素的hash以及key是否相同,若相同则覆盖

      4.2若该数组下标中为红黑树节点,执行红黑树插入

      4.2若数组下标有元素且4.1,4.2不成立,则for循环遍历链表,判断下个元素,如果是尾节点直接放入,如果下个元素与传入元素的hash以及key是否相同,若相同则覆盖

      4.3判断是否要树化,bigcount++>=7? 树化条件 链表元素个数大于8,且数组长度为64

  • ++size是否大于扩容阈值,resize()方法

hash方法

右移16位再异或运算

让key的hashcode高16与低16都参与到数组下标计算过程中去

高位没起到作用,会导致hash冲突变大

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap始终控制数组长度位2的次方数?

tableSizefor()方法控制

就算初始化HashMap传入的参数容量为10,也会初始化后变为10;

为什么要用(n-1)&hash?

其实要计算数组下标最简单是用计算后的扰动hash对数组长度进行取余操作

hash%16=0-15;

  • 计算机底层 进行位运算更快
  • hash%n==(n-1)&hash

get方法

get()方法流程总结:

  • 首先判断桶数组是否为空,若为空直接返回null。
  • 若桶数组不为空,则计算索引,查看当前索引是否有元素,若有元素,查看key值是否相同。若key值相同,则返回该节点。
  • 若key值不相同,则继续查找,先判断是否树化,若已经树化,则调用红黑树的getTreeNode()进行查找。
  • 若没有树化,则从前往后遍历链表,找到了则返回节点,找不到则返回null。
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) {
            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;
    }
  • 为什么我们不用hashCode()方法计算的出来的hash值作为桶下标呢?
    因为hashCode()计算出来的hash值太大,需要大量的存储空间;而且这样哈希表就跟普通的数组差不多。

  • 为什么在hash()中计算桶下标要进行(h>>>16)?
    这样保留了高16位,使得高低16位都参与异或运算,降低了哈希冲突的概率。

树化的条件:

  • 当前桶中的链表长度>=8,并且哈希表的长度>=64时,会树化,否则只是进行简单的扩容。
  • 加入红黑树是为了提高由于链表过长而造成的查询效率降低,将时间复杂度由O(n)提升为O(logn)。

5.扩容的条件:当桶数组的容量>12时进行扩容。

6.解树化的条件:当红黑树的节点个数<=6时,会将红黑树转为链表。

resize方法

有两种情况会触发扩容

  • ++size>threshold 16—12
  • 链表长度大于8且数组长度小于64

数组总是两倍扩容,遍历数组的每个下标位置,原数组中会有四种情况

  • 数组中为空 直接跳过

  • 数组中为单个Node 计算新下标,放到新数组中去

  • 数组中为链表

    ​ 分为高低链插入新数组的下标位置

    if ((e.hash & oldCap) == 0)//低位置下标插入 newTab[j] = loHead;

       if ((e.hash & oldCap) == 1)//高位置下标插入     **newTab[j + oldCap] = hiHead;**
    
  • 数组中为红黑树节点 split()方法

    考虑何时反树化 重新计算下标,高低节点分开后,一侧小于6个节点,TreeNode—Node

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值