HashMap(数组+链表+红黑树)
特征:键不可重复,值可重复,底层Hash表,线程不安全,允许key为null,值为null。
- HashMap根据键的hashcode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。
- HashMap最多允许一条键的值为null,允许多条记录为null。
- HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能导致数据不统一,如果需要满足线程安全可以用Collection的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap
HashMap(java7实现)
大体上HashMap里面是一个数组,然后数组中每个元素是一个单向链表,上图中绿色的实体是嵌套类Entry的实例,Entry包含四个属性:key,value,hash值和用于单向链表的next
- capacity:当前数组容量,始终保持2^n,可以扩容,扩容后数组大小为当前的2倍
- loadFactor:负载因子,默认为0.75
- threshold:扩容的阈值,等于capacity*loadFactor
HashMap(java8实现)
java8最大的不同是运用了红黑树,根据java7HashMap,查找的时候根据hash值能够快速确定数组的具体下标,但是之后需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度为O(n),为了降低这部分的开销,在java8中,当链表中的元素超过8个之后会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为O(logN)
红黑树实现
R-B Tree 全称是Red-Black Tree,又称红黑树,一种特殊的二叉查找树,红黑树的每个节点都有存储位表示节点的颜色,可以是红色或黑色
-
红黑树的特性
(1)每个节点是红色或黑色
(2)根节点是黑色
(3)每个叶子节点(NIL)是黑色[注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点]
(4)如果一个节点是红色的,则它的子节点必须是黑色的
(5)从一个节点到该节点的子孙节点的所有路径上包含相同的黑节点 -
左旋
对x进行左旋,意味着将‘x’的右孩子设为x的父亲节点,即将x变成一个左节点(x成了z的左孩子),因此左旋的左意味着被旋转的节点将变成一个左节点
-
右旋
对x进行右旋,意味着将x的左孩子设为x的父亲节点,即将x变成一个右节点(x成y的右孩子)因此,右旋中的右意味着被旋转的节点变成一个右节点
-
添加
第一步:将红黑树当做一颗二叉查找树,将节点插入。
第二步:将插入的节点着色为红色
根据被插入的节点的父节点情况,可以将“当节点Z被着色成红色节点,并插入二叉树”划分为三种情况来处理
(1)被插入的节点是根节点
处理方法:直接把此节点涂为黑色
(2)被插入的节点的父节点是黑色
处理方法:什么也不做,节点被插入后,仍然是红黑树
(3)被插入的节点的父节点是红色,这种情况下,被插入节点是一定存在非空祖父节点的,进一步讲,被插入的节点也一定存在叔叔节点(即使叔叔节点为空,我们也当他存在,空节点本身就是黑节点),依据叔叔节点的情况,将这种情况进一步划分为3种情况
第三步:通过一系列的旋转或着色操作,使之重新成为一颗红黑树
- 删除
第一步:将红黑树当做一个二叉查找树,将节点删除,和删除二叉查找树一样的方法,分为三种情况
(1)被删除节点没有儿子,即为叶子结点,直接删除就好了
(2)被删除节点只有一个儿子,直接删除节点,并用该节点的唯一子节点顶替它的位置
(3)被删除节点有两个儿子,先找出后继节点,然后将它的后继节点的内容复制给该节点的内容,然后将后继节点删除
第二步:通过旋转和重新着色,使之重新成为红黑树
选择重着色3种情况
(1)x是红+黑节点
处理方法:直接把x设为黑色,
(2)x是黑+黑节点,且x是根
处理方法:什么都不做
(3)x是黑+黑节点,且x不是根
处理方法:这种情况又分为4种子情况,如下图所示
代码实现
- 基本定义
public class RBTree<T extends Comparable<T>>{
private RBTNode<T> mRoot; //根节点
private static final boolean RED = false;
private static final boolean BLACK = true;
public class RBTNode<T extends Comparable<T>>{
boolean color; //颜色
T key; //关键字
RBTNode<T> left; //左孩子
RBTNode<T> right; //右孩子
RBTNode<T> parent; //父节点
public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
this.key = key;
this.color = color;
this.parent = parent;
this.left = left;
this.right = right;
}
}
}
- 左旋
private void leftRotate(RBTNode<T> x){
//设置x的右孩子为y
RBTNode<T> y = x.right;
//将y的左孩子设为x的右孩子
//如果y的左孩子为空,将x设为y的左孩子的父亲
x.right=y.left;
if(y.left!=null)
y.left.parent = x;
//将x的父亲设为y的父亲
y.parent = x.parent;
if(x.parent==null){
this.mRoot=y; //如果x的父节点是空节点,则将y设为根节点
}else{
if(x.parent.left==x)
x.parent.left = y; //如果x是它父节点的左孩子,则将y设为x的父节点的左孩子
else
x.