Map
- Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
HashMap的底层实现原理?以jdk7为例说明:
jdk8 相较于jdk7在底层实现方面的不同:
红黑树与链表的实际复杂度
- 链表 O(N)
- 红黑树 Olog(n)
HashMap存储结构
HashMap源码分析
JDK1.8
HashMap源码重要的常量
- DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
- DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
- threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
- TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
- MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
Node对象(内部类)
- final int hash;
- final K key;
- V value;
- Node<K,V> next;
(应用变量next,用于指向下一个元素,可以看做索引)
TreeNode
初始化构造方法
put 方法详解:
首先调用 hash方法
- hashmap里面的hash方法 ,其中调用了Object;里面的hashcode方法,但并不完全由它决定
- 得到hash值,相同的元素hash值一定相同.
- 但hash值相等的两个元素,不一定相同,还得调用equals方法判断
putVal-方法
- 首先申明一个Node<K,V>[ ] tab ,NOde类型的数组,属性名 tab.
- 判断tab=tanle 是不是空,或者数组的长度是否为0
- 如果是的 调用resize()方法
resize()方法
- 主要用于生成Node[ ]数组,默认初始容量16 ,加载因子0.75
通过hash值计算,得到在Node数组中的存放位置
- i = (n - 1) & hash
计算机与&运算 比 模%运算更高效
- HashMap的容量为什么是2的n次幂,和这个(n - 1) & hash的计算方法有着千丝万缕的关系,符号&是按位与的计算,这是位运算,计算机能直接运算,特别高效,按位与&的计算方法是,只有当对应位置的数据都为1时,运算结果也为1,当HashMap的容量是2的n次幂时,
- (n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞,下面举例进行说明。
案例:
上面四种情况我们可以看出,不同的hash值,和(n-1)进行位运算后,能够得出不同的值,使得添加的元素能够均匀分布在集合中不同的位置上,避免hash碰撞。
终上所述,
HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低!
情况1
如果计算出来的此位置上的数据P为空,则此时的key1-value1添加成功。 ----情况1
如果计算出来的Node数组中的存放位置P是空值:则直接调用构造方法newNode添加元素
- p = tab[i = (n - 1) & hash]) == null
- tab[i] = newNode(hash, key, value, null);
- 如果此位置上的数据不为空
意味着此位置上存在一个或多个数据(以链表形式或者红黑树形式存在)),
- 需要判断这个哈希桶存放位置下面到底存在几个元素,用到for循环遍历
- 此时再比较key1和已经存在的一个或多个数据的哈希值:
情况 2
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。
- (e = p.next) == null) 不存在key元素相等的情况,等于空
直接new 一个node对象存放进去
情况 3
key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同
- p.hash == hash
则再调用key1所在类的equals(key2)方法,进行比较
哈希值相同且equals()返回true:那么此时使用value1替换value2
情况 4
如果哈希值相同且equals()返回false
如果e = p.next) == null,则代表这个哈希桶位置下面所有的元素都遍历完了.且不存在相同的元素key1
那么就直接new 一个node对象存放进去
- 这个Nde对象排在这个链表的最后面
- 这个Nde对象中next属性为null,
(jdk1.8是尾插法,1.7是头插法)
头插法可能在高并发的情况下造成死锁
如果遍历中的元素有哈希值相同且equals()返回true,则break.然后执行和情况3一样的代码.
- 使用value1替换value2
分析:
以上源码分析 ,没有涉及到 如果已转化为红黑树,putTreeVal方法的分析
以上源码分析 ,没有涉及到链表如何转化为红黑树的代码treeifyBin
treeifyBin
treeifyBin
- 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8
- 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储
以上源码分析 ,没有涉及到数组扩容的 resize()方法
resize()
总结: