专注于Java、Golang、软件架构、项目管理

Back

Hashmap#

对key进行hash,放到数组对应的位置 p = tab[i = (n - 1) & hash] 。i的位置为i = (n - 1) & hash

从JDK8以后结构为:数组 + 链表 + 红黑树,从原来的hash冲突时由纯链表变为,当链表长度大于8以后变为红黑树。

fail-fast机制是什么东西,这个在开发的时候是一个比较常见的异常,ConcurrentModificationException

核心变量#

1.8优化#

降低冲突概率的算法#

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

h >>> 16,对h进行无符号右移,即将高位16hash值移到低位。

然后使用^进行异或操作。将高低位异或,计算出来的值减少hash冲突

寻址算法优化#

(length - 1) & hash等价于hash%n(当length为2的倍数时,jdk强制length为2的倍数)。但按位与&比取模算法效率高。

哈希冲突时,链表处理方式#

使用拉链法,在冲突的地方链接一个双向链表。若还有冲突,则继续在链表中插入。

**jdk7使用头插法,jdk8使用尾插法。**优化原因,头插法resize的时候会有并发问题,尾插法则不会。虽然并发访问不适合用此类,但jdk8源码开发者还是改了此处。原因是防止指令重排后,resize和插入元素会有重复XX问题? 待确认

链表大于8时,将链表转换为红黑树#

优化原因,大量哈希冲突,会导致get操作性能急剧下降。链表查询为O(n),转换为红黑树后查询时间变为O(logn)

扩容原理#

2倍扩容,并且rehash。有hash冲突的元素,rehash后的位置变为index+oldCap或位置不变。2倍扩容的方式同样避免了低效的取模运算,使用按位与提高效率

源码:newTab[e.hash & (newCap - 1)] = e;

LinkedHashMap#

插入的时候调用了linkNodeLast方法,插入到了双链表尾部。维护了插入的顺序,遍历的时候会以插入顺序进行输出

TreeMap#

底层将hashmap的数组改为使用红黑树来存放数据,默认使用key的自然顺序来排序,当然也可以指定自定义的排序规则。使用key的排序规则来进行迭代输出

迭代器的fail fast机制#

ConcurrentModificationException,并发修改的异常,这个机制就叫做fail fast.

使用modCount来记录对数据的修改操作,当在迭代的时候修改此迭代器。则会抛出此异常。

因为集合包中的类,都是非线程安全的。所以设计了针对并发修改集合的问题,都有fail fast机制,使用modCount来实现。一旦expectedModCount!=modCount,抛出异常。