集合类
Vector 线程安全
使用sync关键字修饰每一个方法,实现线程安全
优先队列的实现原理,插入及删除描述一下
小根堆的实现, 插入元素时,会将元素放置堆的尾部,依次和父节点比较,若小于父节点的值则与父节点进行交换。 删除同理
modcount的作用
在一些线程不安全的集合中, 会存在并发访问集合的情况。 若集合在遍历的过程中结构发生变化,会带来一些想象不到的异常。jdk 使用了快速失败的思想,每次操作集合数据都会判断当前count并将modcount递增。 遍历的过程中一旦发现此值发生了变化,就抛出异常
hashmap默认容量,负载因子(0.75,泊松分布)作用,默认树化阈值,红黑树的特性、实现(4阶B树)
默认容量是16, 默认树化阈值是8,反树化是6,默认负载因子是0.75
负载因子的作用:
负载因子有两方面的作用:
一是负载因子代表了元素密度,负载因子越大,扩容触发的时机就晚,碰撞几率会增多,会涉及更多的链表或树的插入动作, 查询效率会大大降低。负载因子越小,扩容触发的时机就早,碰撞几率会大大减少,能以较快的方式查询到数据, 但数组可能会变为稀疏数组,增加一部分内存浪费。而且每次扩容涉及数组复制、高低链转移等,也需要一部分开销。 所以需要确定一个值,既能减少碰撞几率,又能合理利用空间较少扩容。
二是Hashmap 采用hash表的方式来存储数据, 相同hash值的元素会放入同一个位置,采用链表或红黑树的方式来处理冲突,但红黑树节点的存储、维护开销几乎是链表的两倍,所以我们需要尽可能的减少出现红黑树的情况。 当map中的数据量达到某一个值时,将数组扩容重新hash,使红黑树退化为链表。
泊松分布是验证一段时间内某一随机事件发生次数的概率,经过实践验证,当负载因子为0.75时,发生碰撞8次的概率为亿分之6,大于8次的概率为千万分之一。 所以hashmap将负载因子确定为0.75,将8作为树化阈值,此情况下基本没有树的形成。
红黑树的特性:
红黑树是一种平衡二叉树的最常见实现, 尽量减少维护树平衡的开销。 保障每个节点到其所有叶子节点的最大距离差不大于2。根据以下几个要求来保障其特性
- 叶子节点为黑色,根节点为黑色
- 不允许出现连续的红节点
- 每个节点到其任一叶子节点的黑节点数量相同
红黑树的实现, 红黑树是4阶B树的一种实现, 4阶B树是指每个节点中至多有三个元素。 一个红黑树对应一个4阶B树,而一个4阶B树对应多个红黑树。 二节点对应红黑树单个黑节点,三节点对应红黑树只有一个子节点,且上黑下红,三节点对应红黑树有两个节点的子树,且上黑下红。 覆盖了红黑树的所有场景。我们以红黑树的插入为例,每个待插入的节点都为红节点
当待插入的树为B树二节点时, 直接插入即可。
当待插入的树为B树三节点时,插入后修复为四节点, 先旋转再变色。 旋转时左倾右旋,右倾左旋。 变色是根节点变黑,子节点变红。
当待插入的树为B树四节点时,比较复杂,设计B树的升元操作,会分裂为一个二节点和一个三节点。
Hashmap 的插入过程,扩容过程
hashmap的插入过程:
先看数组是否初始化, 如果没有初始化进行一下初始化。再计算key的hash值, 通过key的hashcode高低16位异或(扰动函数),减少碰撞概率。 通过key的hash值定位其在数组中的位置, 若该位置没有元素,直接放到这。 如果该位置有元素,说明发生了hash冲突。 处理hash冲突的思路是, 若有key值和当前key值相同,说明发生了数据更新, 直接替换指定value, 若没有当前key值,说明是新增,需要将数据挂到链表或红黑树上。 链表的插入挺简单的,尾插挂上去就行, 红黑树的插入比较麻烦, 涉及一些旋转变色还有升元操作。 链表的插入结束后,会判断当前链表的元素个数,大于阈值会进行链表树化。 整体插入完成后会判断map中的元素总数是否大于阈值。大于阈值会进行扩容。
hashmap的扩容过程:
计算新容量和新阈值
按新容量创建一个新数组,遍历老数组依次迁移。
如果是单个节点,直接重新取余,确定新位置后放下。
如果是链表, 拆分高低链表,高位置放置新位置。 拆分方式为hash值与老容量直接进行且操作, 因为老容量始终是2的整数幂,扩容2倍后hash值会多一位参与取余运算,此方式能快速找出多出的一位是0还是1,若是1移动到新位置。
如果是红黑树,拆分成两棵树, 如果拆分后的树小于反树化阈值6,退化成链表。 按照高低位置重新存放。