1、准备知识
-
HashMap的底层数据结构
Java语言的基本数据结构可以分为两种,一种是数组,另一种的模拟指针/引用,Java语言中涉及到的数据结构都是这两种的扩充。JDK1.8之前HashMap是数组+链表结合的链表散列。JDK1.8在解决哈希冲突上发生了变化,当链表长度大于阈值/默认8的时候,会将链表转化为红黑树,减少搜索时间。

-
hash算法
我们希望HashMap的元素位置尽量分散,最好是每个位置只有一个元素,这样用hash算法求得该位置后可以直接返回结果,不用再遍历链表/红黑树。
HashMap通过Key的hashCode经过扰动函数(就是hash方法)处理后得到hash值,然后在公式(n-1)& hash (/n是数组长度)判断当前元素的位置是否已存在元素,若已存在则判断新加入的元素和已存在的是否相同,相同则覆盖,不相同则拉链法解决冲突。
//扰动函数 == hash方法 /*JDK1.7 此函数可确保仅在以下方面不同的hashCode每个位位置的恒定倍数有界碰撞次数(默认负荷系数下约为8)。 */ static int hash(int h){ h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /*JDK1.8的hash方法比JDK1.7的更加简化,JDK1.7的扰动需要4次,JDK1.8的仅需1次*/ static int hash(Object key){ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }-
拉链法:将链表和数组相结合。也就是说创建⼀个链表数组,数组中每⼀格就是⼀个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。下次查询的时候,需要遍历这个链表找到元素,减慢了查询效率。
-
->所有,在存储大规模的数据时,预先指定HashMap的大小为2的幂次方
int capacity = 1; while(capacity < initialCapacity){ capacity <<= 1;//扩大两倍 }
2、HashMap的resize()方法介绍
-
当HashMap中的元素增多时,发生哈希冲突的几率就越来越高,这时候为了提高查询的效率,需要对数组大小进行扩容。
-
当数组的元素规模 > 数组大小 * loadFactory时进行扩容,loadFactory默认是0.75
... map = new HashMap<>(c, loadFactor); //HashMap提供了以上有参构造方法 //initialCapacity是HashMap数组的初始容量 //loadFactor是加载因子 -
什么时候进行resize?1、初始化table的时候 2、数组元素size超出threshold = map.size() * loadFactory
直接扩容 原数组的2倍大小
-
节点在转移的过程中是一个个节点复制还是一串一串的转移?从源码中我们可以看出,扩容时是先找到拆分后处于同一个桶的节点,将这些节点连接好,然后把头节点存入桶中即可
-
话不多说,上源码
-
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; //如果原table不为空 if (oldCap > 0) { //如果原容量已经达到最大容量了,无法进行扩容,直接返回 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //设置新容量为旧容量的两倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //阈值也变为原来的两倍 newThr = oldThr << 1; // double threshold } /** * 从构造方法我们可以知道 * 如果没有指定initialCapacity, 则不会给threshold赋值, 该值被初始化为0 * 如果指定了initialCapacity, 该值被初始化成大于initialCapacity的最小的2的次幂 * 这里这种情况指的是原table为空,并且在初始化的时候指定了容量, * 则用threshold作为table的实际大小 */ else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; //构造方法中没有指定容量,则使用默认值 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 计算指定了initialCapacity情况下的新的 threshold if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; /**从以上操作我们知道, 初始化HashMap时, * 如果构造函数没有指定initialCapacity, 则table大小为16 * 如果构造函数指定了initialCapacity, 则table大小为threshold, * 即大于指定initialCapacity的最小的2的整数次幂 * 从下面开始, 初始化table或者扩容, 实际上都是通过新建一个table来完成 */ @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { /** 这里注意, table中存放的只是Node的引用,这里将oldTab[j]=null只是清除旧表的引用, * 但是真正的node节点还在, 只是现在由e指向它 */ oldTab[j] = null; //桶中只有一个节点,直接放入新桶中 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; //桶中为红黑树,则对树进行拆分,对树的操作有机会再讲 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //桶中为链表,对链表进行拆分 else { // preserve order //下面为对链表的拆分,我们单独来讲一下。 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }-
resize()操作之链表拆分
/** 这里定义了四个节点:loHead, loTail ,hiHead , hiTail,两个头节点两个尾节点 选择出扩容后在同一个桶中的节点,直接将节点连接好头节点入桶即可。 **/ HashMap.Node<K,V> loHead = null, loTail = null; HashMap.Node<K,V> hiHead = null, hiTail = null; HashMap.Node<K,V> next; //遍历该桶 do { next = e.next; //找出拆分后仍处在同一个桶中的节点 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; }
-
-
4084



被折叠的 条评论
为什么被折叠?



