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

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

这种情况下,就会覆盖原来的值,这个就是我们执行 putAll 后,元素缺失的原因了。


image.png


好了既然问题找到了,那如何解决这个问题呢?


如果是你,你会怎么解决呢?可以花一分钟时间思考一下,再看后面的内容。


4、解决 TreeMap.putAll,元素缺失的问题


我当时想到最直接的方案就是,在 value 相等的情况下,不返回 0,返回1 or -1,这样就可以最简单、最快捷的解决这个问题了。


修改后的代码如下所示:


// 这里换了一种写法,是java8的特性,简化了代码(为了偷懒)
Map<Long, Integer> treeMap2 = new TreeMap<>((key1, key2) -> {
    // 1、如果v1等于v2,则值为0
    // 2、如果v1小于v2,则值为-1
    // 3、如果v1等于v2,则值为1
    Integer value1 = map.get(key1);
    Integer value2 = map.get(key2);
    int result = value1.compareTo(value2);
    if (result == 0) {
        return -1;
    }
    return result;
});
treeMap2.putAll(map);
System.out.println(treeMap2);


运行后的结果为:


{3=10, 1=10, 2=20}


我们可以发现,3个值都有了,并且是有序的,完美符合需求!好了,关机下班!


image.png


然而事情并没有结束 (大家可以想一下,这样写会有什么问题呢?)


新的问题出现


第二天,高高兴兴的写着业务代码、调试逻辑,突然一个 空指针 的报错,出现了。这也太常见了吧,3分钟内解决!


image.png


排查了半天,发现又回到了昨天的修改的那段逻辑了。


1、TreeMap.get 获取不到值


简化版代码如下所示:


// 假设,key=商品id,value=商品剩余库存
Map<Long, Integer> map = new HashMap<>();
map.put(1L, 10);
map.put(2L, 20);
map.put(3L, 10);
// 排序
Map<Long, Integer> treeMap2 = new TreeMap<>((key1, key2) -> {
    Integer value1 = map.get(key1);
    Integer value2 = map.get(key2);
    int result = value1.compareTo(value2);
    if (result == 0) {
        return -1;
    }
    return result;
});
treeMap2.putAll(map);
System.out.println(treeMap2);
// 获取商品1的剩余数量
Integer quantity = treeMap2.get(1L);
System.out.println(quantity);


运行后的结果为:


{3=10, 1=10, 2=20}
null


这个结果令我百思不得其解,只能看看源码咯。


2、分析 TreeMap.get


源码如下所示:


public V get(Object key) {
    // 根据key获取节点
    TreeMap.Entry<K,V> p = getEntry(key);
    // 节点为空则返回null,否则返回节点的 value 值
    return (p==null ? null : p.value);
}
final TreeMap.Entry<K,V> getEntry(Object key) {
    // 一、如果比较器不为空,则执行一下逻辑
    if (comparator != null)
        // 1、使用自定义比较器取出key对应的节点
        return getEntryUsingComparator(key);
    // 二、如果比较器为空,且key为null,则抛空指针异常
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
    Comparable<? super K> k = (Comparable<? super K>) key;
    TreeMap.Entry<K,V> p = root;
    // 三、取出key对应的节点
    while (p != null) {
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            p = p.left;
        else if (cmp > 0)
            p = p.right;
        else
            return p;
    }
    return null;
}


从上面的源码,我们可以发现,问题肯定就是出现在 getEntryUsingComparator 方法里了。


2、分析 TreeMap.getEntryUsingComparator


源码如下所示:


final TreeMap.Entry<K,V> getEntryUsingComparator(Object key) {
    // 一、将key转换成对应的类型
    @SuppressWarnings("unchecked")
    K k = (K) key;
    // 二、获取比较器
    Comparator<? super K> cpr = comparator;
    // 三、判断比较器是否为空
    if (cpr != null) {
        // 1、遍历map,取出key对应的节点对象
        TreeMap.Entry<K,V> p = root;
        while (p != null) {
            int cmp = cpr.compare(k, p.key);
            // 2、如果小于0,则将左节点的值赋值给p
            if (cmp < 0)
                p = p.left;
            // 3、如果大于0,则将右节点的值赋值给p
            else if (cmp > 0)
                p = p.right;
            else
                // 4、如果等于0,则返回p节点
                return p;
        }
    }
    return null;
}


结合上面的源码,和我们之前自定义的比较器,我们不难发现问题出现在哪里:


image.png


自定义比较器,没有返回0的情况


问题找到了,解决吧!

相关文章
|
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的比较器引发的问题...(上)