中招了,重写TreeMap的比较器引发的问题...(上)

简介: 中招了,重写TreeMap的比较器引发的问题...(上)

需求背景


  • 给一个无序的map,按照value的值进行排序,value值越小,排在越前面。
  • key和value都不为null
  • value可能相同
  • 返回结果为一个相同的有序map


代码如下所示:


// 假设,key=商品id,value=商品剩余库存
Map<Long, Integer> map = new HashMap<>();
map.put(1L, 10);
map.put(2L, 20);
map.put(3L, 10);


到这里,大家可以先想想,如果是你会怎么解决?


我的解决思路


1、使用TreeMap,因为TreeMap可以对元素进行排序


2、重写TreeMap的比较器


代码如下所示:


// 承接上面的代码
// 按照 value 排序
Map<Long, Integer> treeMap1 = new TreeMap<>(new Comparator<Long>() {
    @Override
    public int compare(Long o1, Long o2) {
        // 1、如果v1等于v2,则值为0
        // 2、如果v1小于v2,则值为-1
        // 3、如果v1等于v2,则值为1
        Integer value1 = map.get(o1);
        Integer value2 = map.get(o2);
        return value1.compareTo(value2);
    }
});
treeMap1.putAll(map);
System.out.println(treeMap1);


运行后的结果为:


{1=10, 2=20}


what?为什么我们添加了3个元素,结果少了一个呢?


image.png


TreeMap putAll源码分析


让我们来看看 putAll 的具体过程


1、分析 TreeMap.putAll


源码如下所示:


public void putAll(Map<? extends K, ? extends V> map) {
    // 一、获取待添加的map的大小
    int mapSize = map.size();
    // 二、当前的size大小等于0 且 待添加的map的大小不等于0 且 待添加的map是SortedMap的实现类,则执行以下逻辑
    if (size==0 && mapSize!=0 && map instanceof SortedMap) {
        // 1、获取待添加的map的比较器
        Comparator<?> c = ((SortedMap<?,?>)map).comparator();
        // 2、如果两个比较器相同,则执行以下逻辑
        if (c == comparator || (c != null && c.equals(comparator))) {
            // 3、修改次数+1
            ++modCount;
            try {
                // 4、基于排序数据的线性时间树构建算法,进行build
                buildFromSorted(mapSize, map.entrySet().iterator(),
                        null, null);
            } catch (java.io.IOException cannotHappen) {
            } catch (ClassNotFoundException cannotHappen) {
            }
            return;
        }
    }
    // 三、如果不符合上面的条件,则执行父类的 putAll 方法
    super.putAll(map);
}


从上面源码,不难看出,我们的数据符合 流程二,但是不符合 流程二-2,所以我们会执行父类的 putAll 方法,即流程三。


2、分析 AbstractMap.putAll


TreeMap 继承 AbstractMap,所以 super.putAll(map),执行的 putAll 为 AbstractMap 的 putAll 方法,源码如下所示:


public void putAll(Map<? extends K, ? extends V> m) {
    // 遍历 m map,将它所有的值,使用put方法,全部添加到当前的map中
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        put(e.getKey(), e.getValue());
}


这段代码简单,就是一个遍历添加元素的。


但是有一个问题,这里的 put 方法执行的是谁的 put 方法呢?


  • 1、AbstractMap.put


  • 2、TreeMap.put


这里大家可以先思考1分钟,然后再继续往下看。


答案是:


执行的是 TreeMap.put


回答错误 or 不知道真实原因的小伙伴,可以去网上搜搜答案,这里是一个很重要的基础知识点哦。


3、分析 TreeMap.put


源代码如下所示:


public V put(K key, V value) {
    // 一、获取根节点
    TreeMap.Entry<K,V> t = root;
    // 二、判断跟节点是否为空
    if (t == null) {
        // 类型检查 and null 检查
        compare(key, key); // type (and possibly null) check
        // 创建根节点
        root = new TreeMap.Entry<>(key, value, null);
        size = 1;
        // 修改次数加1
        modCount++;
        return null;
    }
    int cmp;
    TreeMap.Entry<K,V> parent;
    // 获取比较器
    Comparator<? super K> cpr = comparator;
    // 三、如果比较器不为空,则执行一下逻辑,即自定义比较器执行逻辑
    if (cpr != null) {
        do {
            // 1、将t节点赋值给parent
            parent = t;
            // 2、比较t节点的key是否与待添加的key相等
            cmp = cpr.compare(key, t.key);
            // 3、如果返回值小于0,则将左子树赋值给t节点,即后续遍历左子树
            if (cmp < 0)
                t = t.left;
            // 4、如果返回值大于0,则将右子树赋值给t节点,即后续遍历右子树
            else if (cmp > 0)
                t = t.right;
            else
                // 5、如果返回值为0,则覆盖原来的值
                return t.setValue(value);
        } while (t != null);
    }
    // 四、如果比较器为空,则执行以下逻辑,即默认执行逻辑
    else {
      // 这部分逻辑,先忽略
    }
    TreeMap.Entry<K,V> e = new TreeMap.Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}


我们结合上面的源码和我们自定义的排序器,就可以发现以下问题:


1、我们比较的是两个 value 的大小,而 value 可能是一样的。


image.png

相关文章
|
3月前
|
Java
"Java排序大揭秘:Comparable与Comparator,究竟有何神秘区别?掌握它们,告别排序难题!"
【8月更文挑战第19天】Java提供Comparable与Comparator两种排序机制。Comparable位于`java.lang`包,定义了`compareTo()`方法以实现类的自然排序;Comparator位于`java.util`包,通过`compare()`方法提供外部定制排序。实现Comparable固定了排序策略,适用于类自带排序逻辑;使用Comparator则可在不改动类的前提下灵活定义多种排序规则,适合多样化的排序需求。选择合适机制可优化排序效率并增强代码灵活性。
26 0
|
6月前
|
算法 搜索推荐 Java
数据结构与算法__冒泡排序__Java外比较器和内比较器(排序专题)
数据结构与算法__冒泡排序__Java外比较器和内比较器(排序专题)
60 0
自定义排序的常用方式
自定义排序的常用方式
|
6月前
|
存储
蓝桥杯-1/14天-数位排序【继承Comparable接口实现排序】
蓝桥杯-1/14天-数位排序【继承Comparable接口实现排序】
|
安全 Java 数据库连接
Java常用类库中(ThreadLocal、Comparable比较器、AutoCloseable、Optional空处理)附带相关面试题
1.ThreadLocal线程独立,2.Comparable比较器与Comparetor,3.AutoCloseable接口,4.Optional空处理
60 0
|
存储 算法 Java
TreeSet类的排序问题
TreeSet支持两种排序方法:自然排序和定制排序。TreeSet默认采用自然排序。1、自然排序    TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间大小关系,然后将集合元素按升序排列,这种方式就是自然排序。
1687 0
Java集合相关学习——元素排序两大接口Comparable和Comparator的应用及区别
Java集合相关学习——元素排序两大接口Comparable和Comparator的应用及区别
Java集合相关学习——元素排序两大接口Comparable和Comparator的应用及区别
|
小程序 Java
Java——使用集合实现简单的斗地主发牌功能(两种方式简单粗暴!!!)
Java——使用集合实现简单的斗地主发牌功能(两种方式简单粗暴!!!)
Java——使用集合实现简单的斗地主发牌功能(两种方式简单粗暴!!!)
中招了,重写TreeMap的比较器引发的问题...(下)
中招了,重写TreeMap的比较器引发的问题...(下)
中招了,重写TreeMap的比较器引发的问题...(下)