Java TreeMap(比较器)和get方法忽略比较器

8
public final Comparator<String> ID_IGN_CASE_COMP = new Comparator<String>() {

        public int compare(String s1, String s2) {
            return s1.compareToIgnoreCase(s2);
        }
    };

private Map< String, Animal > _animals = new TreeMap< String, Animal >(ID_IGN_CASE_COMP);

我的问题是,如何在忽略给定比较器的情况下使用get(id)方法。我希望地图按大小写不敏感的顺序排序,但是当我通过给定的键获取值时,我希望它区分大小写。


1
这怎么可能工作呢?TreeMap需要表示两个不同的排序方式,但它无法做到。 - jitter
2
短评:您不必创建此Comparator<String>,它已经存在于java.lang.String类中:String.CASE_INSENSITIVE_ORDER - Pierre
我看不出问题。它将会区分大小写地通过字符串键获取值。 - EJB
通过覆盖get方法不是可以实现吗? - d0pe
7个回答

8

我认为答案很简单。实现一个自己的比较器,可以进行不区分大小写的排序,但是对于"A" 和 "a" 不要返回0,也将它们排序。

问题在于你的比较器对于 compare("A", "a") 的情况返回了0,这意味着在地图中它们是相同的键。

使用如下比较器:

public final Comparator<String> ID_IGN_CASE_COMP = new Comparator<String>() {

    public int compare(String s1, String s2) {
        int result = s1.compareToIgnoreCase(s2);
        if( result == 0 )
            result = s1.compareTo(s2);
        return result;
    }
};

无论大小写,所有键都将被加入,"a"和"A"仍然会被一起排序。

换句话说,get("a")将给你一个不同于get("A")的值...并且它们都会出现在keySet()迭代器中。它们只是被一起排序。


这听起来不错,谢谢。我会测试一下,因为我意识到当我使用put方法时也需要考虑这个问题。因为我还需要区分"A"和"a"。我会测试并告诉你结果。 - d0pe
非常感谢,它解决了我的两个问题。这正是我想要的答案。再次感谢 :) - d0pe
很高兴它起作用了。有时候简单的答案就是正确的。 :) - PSpeed
使用Google Collections库: 静态最终比较器<String> ID_IGN_CASE_COMP = Ordering.from(String.CASE_INSENSITIVE_ORDER).compound(Ordering.natural()); - Kevin Bourrillion

7
在TreeMap中,按顺序添加两个键a和b(以此顺序),使得compare(a, b)返回0会导致最后添加的条目(b)覆盖第一个条目(a)。
在您的情况下,这意味着永远不会使用不区分大小写的get(id)。
引用http://java.sun.com/javase/6/docs/api/java/util/TreeMap.html 请注意,排序映射(无论是否提供显式比较器)维护的排序必须与equals一致,如果该排序映射要正确实现Map接口。 (有关与equals一致的精确定义,请参见Comparable或Comparator。)这是因为Map接口是根据equals操作定义的,但是地图使用其compareTo(或compare)方法执行所有键比较,因此通过此方法被视为相等的两个键从排序映射的角度来看是相等的。即使排序映射的排序与equals不一致,其行为也是明确定义的;它只是未遵守Map接口的一般契约。
这可能不是您想要的。
如果地图相对较小,且您不需要多次获取排序后的条目,则可以使用HashMap(或未明确设置比较器的TreeMap),并在需要排序时以不区分大小写的方式对条目进行排序。

1

也许它能完成这个任务:

    new Comparator<String>(){
    public int compare(String s1, String s2)
    {
        String s1n = s1.toLowerCase();
        String s2n = s2.toLowerCase();

        if(s1n.equals(s2n))
        {
            return s1.compareTo(s2);
        }
        return s1n.compareTo(s2n);
    }
};
                                                    }

+1 你需要将每个字符转换为小写,而不是整个字符串。但方向是正确的。这与大小写不敏感比较器的行为不同(对于大小写不敏感的比较器,键"a"和"A"相等)。 - Thomas Jung

1
你需要使用两个不同的TreeMap,它们具有相同的内容但是使用不同的比较器。

这只是占用太多空间了...我宁愿获取一组键并进行比较,但我正在寻找更简单的方法。 - d0pe

1
你需要一个multimap:每个multimap的条目都保留了不区分大小写的键和另一个以原始键为值的映射。
有许多可自由使用的multimap实现,例如Common CollectionsGoogle Collections等。

0
除了其他答案并且同意,无法使用不同的比较器拥有单个TreeMap结构:
从您的问题中我理解到您有两个要求:数据模型应该区分大小写(在使用get()时您想要区分大小写的值),演示者应该不区分大小写(您想要一个区分大小写的排序,演示只是一种假设)。
让我们假设,我们用映射(aa,obj1),(aA,obj2),(Aa,obj3),(AA,obj4)填充Map。迭代器将按顺序提供值:(obj4,obj3,obj2,obj1)(* )。现在,如果地图按不区分大小写排序,您期望什么顺序?所有四个键都相等,顺序未定义。或者您是否正在寻找解决方案,以便为键“AA”解析集合{obj1,obj2,obj3,obj4}?但这是一种不同的方法。
SO鼓励社区诚实:因此,此时我的建议是再次查看您的要求 :) (*)未经测试,假定'A'<'a'= true。

-1

使用floorEntry,然后在循环中使用higherEntry来不区分大小写地查找条目;当您找到精确的键匹配时停止。


这个到底是怎么工作的?我知道 floorEntry 会返回我搜索的键,但是如果搜索的键是 "aa1",它可能会找到 "Aa1" 或者 "AA1",高一级的 entry 可能会给我另一个。 - d0pe
你是正确的 - 这不起作用,因为你需要使用区分大小写比较器的映射(这不是你想要的),以key.toLowerCase(Locale)开始也不能可靠地适用于不同的语言。接受的答案是获得您想要的方式。 - Andrew Duffy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接