TreeMap
.TreeMap跟TreeSet底层原理一样,都是红黑树结构的
.由键决定特性:不重复、无索引、可排序
.可排序:对键进行排序
.注意:默认按照键从小到大进行排序,也可以按照自己规定键的排序规则
代码书写两种排序规则:
1.实现Comparable接口,指定比较规则
2.创建集合时传递Compartor比较器对象,指定比较规则
Comparable
接口是Java集合框架的一部分,它允许对象定义它们自己的自然排序顺序。实现Comparable
接口的类需要重写compareTo(T o)
方法
//下面是Integer实现Comparable接口重写的方法,可以看到java给这些对象都已经写好了自然排序的方法
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);}
Comparator接口:定义对象的排序顺序,允许你为一个类定义多个不同的排序顺序,或者为那些没有实现
Comparable
接口的类定义排序顺序。说白了,就是支持自定义排序,满足你的排序需求。
//这里的比较方法就是自定义的
TreeMap ts=new TreeMap<>(new Comparator() {
@Override
//o1当前添加元素
//o2表示已经在存在的元素
public int compare(String o1, String o2) {
int i=o1.length()-o2.length();
//一样则按首字母排序
i=i==0?o1.compareTo(o2):i;
return i;
}
});
ts.put("ab","1");
ts.put("abc","1");;
ts.put("c","1");
ts.put("tad","1");
源码解析:
Entry(K key, V value, Entry parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
public V put(K key, V value) {
//获取根节点的地址值,赋值给局部变量
Entry t = root;
//判断根节点是否为null
//如果为null,表示时第一次添加,会把当前添加的元素,当作根节点
//如果不为null,表示当前不是第一次添加,跳过这个判断继续执行下面的代码
if (t == null) {
compare(key, key); //第一次就是和自身比较
root = new Entry<>(key, value, null);//创建一个entry对象,把其当作根节点,null代表没有父节点
size = 1;
modCount++;
return null;
}
//当添加不是首个节点
int cmp;//代表两个键比较之后的结果
//表示要添加节点的父节点
Entry parent;
//当前的比较规则
//规则:
//1.默认的自然排序,那么此时compartor记录时Null
//2.采取比较器排序方式,campartor记录的就是比较器
Comparator super K> cpr = comparator;
//如果没有传递比较器对象,就执行if里面的代码,此时以比较器为准
//如果没有传递比较器对象,就执行else里面的代码,此时以自然排序为准
if (cpr != null) { //设置了比较器的话,进入if语句
do {
parent = t;//把当前节点赋值给父节点,可以再父节点下插入添加的节点
cmp = cpr.compare(key, t.key);//比较当前节点的键值和要添加的剪枝 key代表要添加的键值
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
//键不能为空
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
//键进行强转
//键必须实现Comparable接口,如果没有实现这个接口,就会报错
Comparable super K> k = (Comparable super K>) key;
do {
//把根节点当成当前添加节点的父节点
parent = t;
//调用compareTo方法,比较根节点和当前要添加节点的大小关系
cmp = k.compareTo(t.key); //Integer、String等内部都实现了compareTo这个方法
//如果比较结果时负数,到根节点的左边去找
if (cmp < 0)
t = t.left;
//比较结果是正数,就到根节点的右节点去找
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);//对节点的值进行覆盖,覆盖的方式和hashMap一样,然后直接返回
} while (t != null);//会判断当前的t是不是null
}
//找到插入的位置之后,创建一个键值对对象
Entry e = new Entry<>(key, value, parent);
//根据位置插入新的键值对节点
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);//插入之后需要按照红黑树的规则进行调整
size++;//插入之后节点+1
modCount++;
return null;
}
红黑树
红黑树红黑规则:
1.每一节点要么是红色,也么是黑色
2.根节点必须是黑色
3.如果一个节点没有子节点或者父节点,则该节点的指针属性为Nil,这些NIl视为叶节点,每个叶节点(Nil)是黑色的;
4.不能出现两个红色节点相连的情况
5.对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
插入规则:
假设我们现在的情况是这种
private void fixAfterInsertion(Entry x) {
x.color = RED;//首先我们要先把插入的节点设置为红色的
//按找红黑树的规则进行插入
//1.如果插入的节点是根节点,直接到最后一步root.color = BLACK;
//2.如果我们要插入的节点不是根,就比如是如图所示的(3,c)则进入下面的while循环
while (x != null && x != root && x.parent.color == RED) {
//parentof:获取当前节点的父节点
//parentof(parentof(x)) 获取当前节点的爷爷节点
//leftof获取左子节点
//从上面的图中可以看到leftOf(parentOf(parentOf(x)))//如果相同代表父节点是爷爷节点的左节点,那么叔叔节点就是右节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry y = rightOf(parentOf(parentOf(x)));//获取叔叔节点
if (colorOf(y) == RED) {//如果满足就代表叔叔节点是红色的 按照上面修改规则的规则
setColor(parentOf(x), BLACK); //将父设为黑色
setColor(y, BLACK);//将叔叔设置为黑色
setColor(parentOf(parentOf(x)), RED);//爷爷节点设置为红色
x = parentOf(parentOf(x));//把爷爷节点设置为当前节点再进行判断
//如果爷爷为根节点,直接跳出
//非跟,在进行其他判断
} else {
if (x == rightOf(parentOf(x))) {//当前节点是否为父节点右子节点
x = parentOf(x);//把父节点作为当前节点
rotateLeft(x);//左旋操作,在进行其他判断
}//左旋之后就满足 叔叔是当前父节点的左孩子,就满足图中灰色的部分
setColor(parentOf(x), BLACK);//把父节点设置为黑色
setColor(parentOf(parentOf(x)), RED);//祖父设置为红色
rotateRight(parentOf(parentOf(x)));//以祖父为支点进行右旋,在进行判断
}
} else {
//第二种情况就是获取叔叔节点是左节点
//当前父节点是爷爷节点的右子节点
Entry y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) { //如果叔叔节点是红色
setColor(parentOf(x), BLACK);//父节点设置为黑色
setColor(y, BLACK);//叔叔设置为黑色
setColor(parentOf(parentOf(x)), RED);//将祖父设置红色
x = parentOf(parentOf(x));//将祖父节点设置为当前节点继续判断
} else {
if (x == leftOf(parentOf(x))) {//叔叔黑色并且为父节点的左孩子
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);//父节点设置为黑色
setColor(parentOf(parentOf(x)), RED);//祖父设置为红色
rotateLeft(parentOf(parentOf(x)));//以祖父为支点进行左旋,在进行判断
}
}
}
root.color = BLACK;
}
常见的面试问题:
1.TreeMap添加元素的时候,键是否需要重写hashCode和equals方法?
TreeMap添加put方法时,只是进行键的比较,并没有用hashCode和equals方法。
2.HashMap时哈希表结构的,JDK8时右数组,链表,红黑树组成。
既然有红黑树,是否需要利用Compareable指定排序规则或者是否需要传递比较器comparator指定比较规则
不需要,因为HashMap的顶层,默认是利用哈希值的大小来创建红黑树
3.TreeMao和HashMap书的效率更高?
比如添加元素,最坏情况下,添加多个元素,形成链表的话,此时TreeMap的效率更高,但是这种这种情况的几率非常少。一般而言,还是HashMap的效率更。
4.你觉得Map集合中,java会提供一个如果键重复了,不会覆盖put的方法吗?
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }//可以看到,之前讲解第三个参数,代表键重复是否会进行覆盖操作,这里是有开关的,所以支持覆盖的put方法
一般代码的逻辑可能有多面性
5.三种双列集合,何种选择?
默认:HashMap(效率最高)
如果保证存取有序:LinkedHashMap
如果要进行排序:TreeMap