2010-06-22 64 views
16

我想知道某个特定的键是否存在于HashMap中,所以我使用containsKey(key)方法。但它是区分大小写的,即如果名称中有一个键并且我正在搜索名称,它不会返回true。那么有没有什么办法可以在不打扰钥匙的情况下知道?如何检查映射中的键而不管情况如何?

谢谢

回答

26

不符合传统的地图。

“abc”是与“ABC”不同的字符串,它们的哈希码不同,它们的equals()方法将相对于彼此返回false。

最简单的解决方案是在插入/检查前简单地将所有输入转换为大写(或小写)。你甚至可以编写你自己的Map包装,这将确保一致性。

如果您想维护所提供的密钥的大小写,但不区分大小写,可以使用TreeMap并提供您自己的比较器来比较不区分大小写。然而,在走下这条路线之前,请仔细想一想,因为将会由于而导致一些不可调和的不一致 - 如果有人拨打map.put("abc", 1)然后map.put("ABC", 2),那么存储在地图中的密钥是什么?你甚至可以让这有意义吗?您是否满意这样一个事实,即如果有人将您的地图用标准HashMap你会失去功能?或者,如果有人碰巧通过你的密钥集进行迭代,并通过使用equals()自己快速“包含”检查,你会得到不一致的结果?还会有很多其他类似的情况。 请注意,您违反了Map的合同(因为关键相等性为defined in terms of the equals() method on the keys),所以在任何意义上它都不可行。

维护一个严格的大写地图是很多更容易使用和维护,并具有实际上合法的Map实现的优势。

+0

同意最好的方法是只使用大写或小写插入。 – bwawok 2010-06-22 12:10:40

+2

不幸的是,转换为大写/小写失败“土耳其测试”(谷歌,看看发生了什么字母'我')。如果国际化很重要,那么最好使用'TreeMap'并且是 - 请注意合同违规问题。 (IIRC,使用某些集合类调用'remove/retainAll'可能会带来意想不到的结果。)更好的是,在可能的情况下使用Guava的ImmutableSortedMap/Set,这可以避免给定一个自定义比较器时的不一致行为。 – 2015-12-27 21:43:32

3

Map使用equalshashCode来测试关键相等性,并且您不能覆盖这些关于String。你可以做的是定义你自己的Key类,它包含一个字符串值,但以不区分大小写的方式实现equalshashCode

13

您可以使用TreeMap与自定义,不区分大小写Comparator(使用String.compareToIgnoreCase()

例如:

Map<String, Something> map = 
    new TreeMap<String, Something>(CaseInsensitiveComparator.INSTANCE); 

class CaseInsensitiveComparator implements Comparator<String> { 
    public static final CaseInsensitiveComparator INSTANCE = 
      new CaseInsensitiveComparator(); 

    public int compare(String first, String second) { 
     // some null checks 
     return first.compareToIgnoreCase(second); 
    } 
} 

更新:看起来String有一个已经将此Comparator定义为常量。

+1

'TreeMap'在这种情况下有点狡猾,因为它很容易违反'Map'的约定。 'containsKey()'应该“返回true当且仅当这个映射包含一个键'k'的映射,使得'(key == null?k == null:key.equals(k))''但当然它不会在这种情况下。如果地图以最普通的方式使用,某些时候这将导致不一致。 – 2010-06-22 11:54:28

+0

@Andrzej是真实的,但并不比其他一些违反“Map”合同的行为,例如, 'IdentityHashMap ','TreeMap ' – finnw 2010-06-22 13:50:45

+0

@Andrzej:只要你承认它没有'Map'定义的语义,就没问题。在'SortedMap' javadoc中已经声明了这一点:*请注意,如果有序映射要正确实现Map接口,那么由有序映射(无论是否提供显式比较器)维护的排序必须与equals保持一致。如果它们不一致,它仍然有效,它只是不再遵守'Map'。 – 2010-06-22 14:46:54

10

使用TreeMap,其使用String#CASE_INSENSITIVE_ORDER构建。

Map<String, String> map = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); 
map.put("FOO", "FOO"); 

System.out.println(map.get("foo")); // FOO 
System.out.println(map.get("Foo")); // FOO 
System.out.println(map.get("FOO")); // FOO 
+1

有一个潜伏在这里的bug,不常见但是破碎了:调用map.keyset( )。removeAll(c)'可能会或可能不会使用地图的比较器 - 取决于c的大小!参见[Sun bug 6394757](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6394757)。 (我仍然使用它)。除此之外:番石榴的'ImmutableSorted(Map | Set)'集合让你的大脑休息,严格来说只使用比较器,永远不会等于。 – 2013-10-04 22:07:13

0

创建自己的字符串类的包装,实现equals和hashCode,以此作为在HashMap中的关键:

class MyStringKey 
    { 
     private String string; 
     public String getString() 
     { 
     return string; 
     } 
     public void setString(String string) 
     { 
     this.string = string; 
     } 

     public boolean equals(Object o) 
     { 
     return o instanceof MyStringKey && this.equalsIgnoreCase(((MyStringKey)o).getString()); 
     } 

     public boolean hashCode() 
     { 
     return string.toLowerCase().hashcode(); //STRING and string may not have same hashcode 
     } 
    } 
+0

这可能会更好,使关键不可变 – finnw 2010-06-22 13:48:14

6

要保留Map不变,你可以只让自己的钥匙。实施合理的hashCode/equals,你是好去:

final class CaseInsensitive { 
    private final String s; 
    private final Local lc; 
    public CaseInsensitive (String s, Locale lc) { 
     if (lc == null) throw new NullPointerException(); 
     this.s = s; 
     this.lc = lc; 
    } 

    private s(){ return s == null ? null : s.toUpperCase(lc); } 

    @Override 
    public int hashCode(){ 
     String u = s(); 
     return (u == null) ? 0 : u.hashCode(); 
    } 

    @Override 
    public boolean equals(Object o){ 
     if (!getClass().isInstance(o)) return false; 
     String ts = s(), os = ((CaseInsensitive)other).s(); 
     if (ts == null) return os == null; 
     return ts.equals(os); 
    } 
} 

// Usage: 
Map<CaseInsensitive, Integer> map = ...; 
map.put(new CaseInsensitive("hax", Locale.ROOT), 1337); 
assert map.get(new CaseInsensitive("HAX", Locale.ROOT) == 1337; 

注:在全世界并不是每个人都同意关于什么是什么的大写字母 - 一个著名的例子是,“大写版本我“在土耳其语中是”İ“,而不是”我“。

+0

添加一个'Locale'参数,我会看到这个 – finnw 2010-06-22 13:45:40

+0

完成。我甚至有一个在早期版本... – gustafc 2010-06-22 14:32:13

+0

小(可能天真)的问题:1.为什么不使用ts.s()。equalsIgnoreCase()? 2.为什么不是运营商的实例? – Nivas 2010-06-23 07:33:17

2

最简单的方法是在插入键并查找时自己折叠键。即

map.put(key.toLowerCase(), value); 

map.get(key.toLowerCase()); 

你可以继承例如如果你想自动完成这些,使用HashMap来获得你自己的类。

0

在试图给出符合你的问题的要求“而不打扰键的情况下,”答案...

如果您在很多,很多地方添加到您的地图这样的回答可能是乏味的。在我的例子中,它只发生在用户创建一个新角色时(在我的游戏中)。下面是我如何处理这个问题:

boolean caseInsensitiveMatch = false; 
for (Map.Entry<String, Character> entry : MyServer.allCharacterMap.entrySet()) { 
    if (entry.getKey().toLowerCase().equals(charNameToCreate.toLowerCase())){ 
     caseInsensitiveMatch = true; 
     break; 
    } 
} 

当然这需要遍历我的大型ConcurrentHashMap,但对我有用。

相关问题