【发布时间】:2010-06-22 11:41:51
【问题描述】:
我想知道 HashMap 中是否存在特定键,所以我使用 containsKey(key) 方法。但它是区分大小写的,即如果有一个带有名称的键并且我正在搜索名称,它不会返回 true。那么有什么方法可以让我知道而不用担心钥匙的情况吗?
谢谢
【问题讨论】:
标签: java
我想知道 HashMap 中是否存在特定键,所以我使用 containsKey(key) 方法。但它是区分大小写的,即如果有一个带有名称的键并且我正在搜索名称,它不会返回 true。那么有什么方法可以让我知道而不用担心钥匙的情况吗?
谢谢
【问题讨论】:
标签: java
传统地图不行。
“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),所以它在任何意义上确实不可行。
维护一个严格的大写地图更容易使用和维护,并且具有实际上是合法地图实现的优势。
【讨论】:
TreeMap,是的 - 请注意合同违规问题。 (IIRC,使用某些集合类调用 remove/retainAll 可能会产生意想不到的结果。)更好的是,尽可能使用 Guava 的 ImmutableSortedMap/Set,这样可以避免在给定自定义比较器时出现不一致的行为。
使用由String#CASE_INSENSITIVE_ORDER 构造的TreeMap。
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
【讨论】:
map.keyset().removeAll(c) 的调用可能会或可能不会使用地图的比较器 - 取决于 c 的大小!见Sun bug 6394757。 (我仍然使用它。)除此之外:Guava 的 ImmutableSorted(Map|Set) 集合让您放心,严格只使用比较器,从不等于。
TreeMap,与 HashMap 相比,性能为 O(log n) 而为 O(1)
您可以将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 定义为常量。
【讨论】:
TreeMap 在这种情况下有点狡猾,因为很容易违反Map 的合同。 containsKey() 应该“当且仅当这个映射包含一个键 k 的映射使得 (key==null ? k==null : key.equals(k)) 时返回 true”,但在这种情况下它当然不会。如果地图以最微不足道的方式使用,这将在某些时候导致不一致。
Map 合同的行为更糟糕,例如IdentityHashMap<Object, V>, TreeMap<Double, V>
Map 定义的语义就可以了。 SortedMap javadoc 中已经说明了这一点:请注意,如果排序映射要正确实现 Map 接口,则排序映射维护的顺序(无论是否提供显式比较器)必须与 equals 一致。 。如果它们不一致,它仍然有效,只是不再遵守Map。
Comparator,但不要告诉我们它是什么:String.CASE_INSENSITIVE_ORDER
在 Apache commons 中有一个 CaseInsensitiveMap class
【讨论】:
要保留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;
注意:并不是全世界的每个人都同意什么是大写的——一个著名的例子是土耳其语中“i”的大写版本是“İ”,而不是“I ”。
【讨论】:
Locale 参数,我会支持这个
Map 使用equals 和hashCode 来测试密钥是否相等,您不能为String 覆盖这些。您可以做的是定义您自己的包含字符串值的 Key 类,但以不区分大小写的方式实现 equals 和 hashCode。
【讨论】:
最简单的方法是在插入和查找时自行折叠钥匙。即
map.put(key.toLowerCase(), value);
和
map.get(key.toLowerCase());
你可以继承例如HashMap 来获得你自己的类,如果你想自动完成的话。
【讨论】:
创建你自己的字符串类包装器,实现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
}
}
【讨论】:
试图提供符合您问题要求的答案“不打扰密钥的大小写”...
如果您在很多很多地方添加到地图中,这个答案可能会很乏味。在我的示例中,它仅在用户创建新角色时发生(在我的游戏中)。以下是我的处理方式:
boolean caseInsensitiveMatch = false;
for (Map.Entry<String, Character> entry : MyServer.allCharacterMap.entrySet()) {
if (entry.getKey().toLowerCase().equals(charNameToCreate.toLowerCase())){
caseInsensitiveMatch = true;
break;
}
}
当然,这需要遍历我的大型 ConcurrentHashMap,但对我有用。
【讨论】: