TreeMap排序时,使用不规范的比较器引发的问题

(此博客编写于2015年草稿)
刚开始工作时用过TreeSet+Comparator实现按添加顺序排序的情况(当然现在用LinkHashSet)。比较器的定义是直接return 1 :

	public static void testTreeSet(){
		Set<String> set = new TreeSet<>(new Comparator<String>() {
			/**
			 * 虽然该方法不符合规定,但能按添加顺序排列元素。(具体规范请参见JDK文档)
			 */
			@Override
			public int compare(String o1, String o2) {
				return 1;
			}
		});;
		set.add("hahahah");
		set.add("aaaaa");
		set.add("xxxxxx");
		set.add("bbbbb");
		System.out.println(set); //print [hahahah, aaaaa, xxxxxx, bbbbb],排序成功
	}

由于Set底层的结构和Map是一样的:

        /*
	 * TreeSet中m为NavigableMap(TreeMap的父接口),HashSet中m为HashMap。
	 */ 
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }


于是乎同样用TreeMap试了试,结果发现一个小问题,这个问题是由TreeMap底层的二叉树结构引起的。测试代码:

public static void main(String[] args) {
		Map<String,String> map = new TreeMap<String,String>(new Comparator<String>() {
			/**
			 * 同样不符合规定,但这时产生了问题
			 */
			@Override
			public int compare(String o1, String o2) {
                //if(o1.equals(o2)) return 0; //这一行先注释
				return 1;
			}
		});
		map.put("ss", "abc1");
		map.put("s2", "abc2"); 
		map.put("hahah2", "abc3");
		map.put("abcdefg", "abc4");
		System.out.println(map.keySet()+"-----" + map.values()); //print:[ss, s2, hahah2, abcdefg]-----[abc1, abc2, abc3, abc42]
		System.out.println(map.get("ss"));	//print:null
<span style="white-space:pre">		</span>System.out.println(map.get("hahah2"));  //print:null, 但打开注释后print:abc3
	}

这里的问题就是 为什么明明添加成功了但map.get("ss")却返回null?事实上这种情况下(不打开注释)获取任何一个key的值都会返回null。原因就在于当get(key)时这里会调用我们自定义的比较器比较key(return 0时表示存在),由于compare总返回1, 所以永远都找不到对应结点,则返回null。

打开注释后发现 其它元素却已经可以获取了,为什么map.get("ss")仍然返回null?这是因为TreeMap底层采用的是二叉树结构,其put方法与get方法如下:

 public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {   //第一次添加把当前元素作为根结点
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.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<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent); //创建一个新的结点,并设置节点间关系
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);  //调整红蓝二叉树的位置、颜色等
        size++;
        modCount++;
        return null;
    }

public V get(Object key) {
    Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
}

//......
//......
final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    if (comparator != null)
        return getEntryUsingComparator(key);	//调用自定义比较器的比较方法
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;
    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;
}

//......
//......
final Entry<K,V> getEntryUsingComparator(Object key) {
    @SuppressWarnings("unchecked")
        K k = (K) key;
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = cpr.compare(k, p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;	//等于0时才返回对应的节点(找到相同的key)
        }
    }
    return null;<span style="white-space:pre">	</span>//找不到返回null
}

通过跟踪代码中的map发现其根结点为s2,而不是第一个被添加的元素ss,由于“ss”.equals("s2")为false, 所以get("ss")时的比较器永远返回1(代表着只在根结点的右边进行检索),所以get("ss")返回null,而其它key可以正常返回。 map结构图类似如下:


总结:TreeSet可以使用这种方式实现按添加顺序排序(虽然Set底层也是Map结构,但它的元素是底层map中的key,与value无关,不需要get(key)。而TreeMap使用这种方式虽然也能实现按添加顺序排序,但不能这么干。因为其取值时的检索方式不同,很可能导致无法获取value。这种方式是不规范的,应使用LinkHashSet或LinkHashMap。

相关链接:http://blog.csdn.net/chenssy/article/details/26668941

http://www.tuicool.com/articles/6ZvEVbI

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值