链表查询的时间复杂度是O(n),红黑树的查询复杂度是O(log(n)。在链表数据不多的时候,使用链表进行遍历也比较快,只有当链表数据比较多的时候,才会转化成红黑树,但红黑树需要的占用空间是链表的2倍,考虑到转化时间和空间损耗,所以我们需要定义出转化的边界值,链表结点>=8时才进行树化。
HashMap查找步骤:
链表查找 key是自定义类时需要重写equals方法(来比较链表结点值是否相等)
// 采用自旋方式从链表中查找 key,e 初始为为链表的头节点 do { // 如果当前节点 hash 等于 key 的 hash,并且 equals 相等,当前节点就是我们要找的节点 // 当 hash 冲突时,同一个 hash 值上是一个链表的时候,我们是通过 equals 方法来比较 key 是否相等的 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; // 否则,把当前节点的下一个节点拿出来继续寻找 } while ((e = e.next) != null);红黑树查找 key是自定义类时需要重写compator方法(来判断红黑树往左子结点走还是往右走)
使用异或计算hash,拿高16位异或低16位
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } key 在数组中的位置公式:tab[(n - 1) & hash]h^(h>>>16),这么做的好处是使大多数场景下,算出来的hash值比较分散。
hash 值算出来之后,要计算当前key在数组中的索引下标位置时,可以采用对数组长度取模,但是取模操作对于处理器的计算是比较慢的,数学上有个公式,当b是2的幂次方时,a%b=a&(b-1),所以此处索引位置的计算公式我们可以更换为:(n-1)&hash。
因为树有可能退化到链表状态,所以红黑树是一个二叉平衡树,通过自旋来调整高度
getOrDefault方法:如果 key 对应的值不存在,返回期望的默认值 defaultValue
public V getOrDefault(Object key, V defaultValue) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? defaultValue : e.value; }putlfAbsent(K key,V value):如果map中存在key了,那么value就不会覆盖,如果不存在key,新增成功。
compute 方法:允许我们把 key和value的值进行计算后,再put到map中,为防止key值不存在造成未知错误,