java----TreeMap

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<String,String> ts=new TreeMap<>(new Comparator<String>() {
            @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<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
}
public V put(K key, V value) {
        //获取根节点的地址值,赋值给局部变量
        Entry<K,V> 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<K,V> 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<K,V> 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<K,V> 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<K,V> 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<K,V> 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值