出于以下原因,我想使用不区分大小写的字符串作为HashMap键。
<key, value>
。我遵循了这种方法
CaseInsensitiveString.java
public final class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
public boolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
}
private volatile int hashCode = 0;
public int hashCode() {
if (hashCode == 0)
hashCode = s.toUpperCase().hashCode();
return hashCode;
}
public String toString() {
return s;
}
}
LookupCode.java
node = nodeMap.get(new CaseInsensitiveString(stringFromEvent.toString()));
因此,我为每个事件创建一个CaseInsensitiveString新对象。因此,它可能会影响性能。
还有其他解决方法吗?
Map<String, String> nodeMap =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
这实际上就是您所需要的。
这是我为最近的项目实现的HashMaps适配器。其工作方式与@SandyR相似,但是封装了转换逻辑,因此您不必手动将字符串转换为包装对象。
我使用了Java 8功能,但进行了一些更改,您可以使其适应以前的版本。除了新的Java 8流功能之外,我已经针对大多数常见场景进行了测试。
基本上,它包装HashMap,在将字符串转换到包装对象或从包装对象转换字符串时,将所有函数定向到它。但是我还必须改编KeySet和EntrySet,因为它们会将一些函数转发到地图本身。因此,我为键和条目返回了两个新的Set,它们实际上包装了原始的keySet()和entrySet()。
注意:Java 8改变了putAll方法的实现,我找不到简单的方法来覆盖它。因此,当前的实现可能会降低性能,尤其是如果您对大型数据集使用putAll()。
[如果您发现错误或有改进代码的建议,请告诉我。
包webbit.collections;
get(String, Locale)
因此,我为每个事件创建一个CaseInsensitiveString新对象。因此,它可能会影响性能。
在查找之前创建包装器或将键转换为小写字母都会创建新对象。编写自己的java.util.Map实现是避免这种情况的唯一方法。这并不难,海事组织值得。我发现以下哈希函数可以很好地工作,最多可以使用数百个键。
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s, Locale locale) {
this.s = s.toUpperCase(locale);
}
// equals, hashCode & toString, no need for memoizing hashCode
}
如何使用Java 8流。
https://github.com/jdereg/java-util
如GuidoGarcía在their answer here中所建议:
import java.util.HashMap;
public class CaseInsensitiveMap extends HashMap<String, String> {
@Override
public String put(String key, String value) {
return super.put(key.toLowerCase(), value);
}
// not @Override because that would require the key parameter to be of type Object
public String get(String key) {
return super.get(key.toLowerCase());
}
}
或
一种方法是创建Apache Commons AbstractHashedMap
类的自定义子类,重写AbstractHashedMap
和hash
方法以执行不区分大小写的哈希和键比较。 (注意-我从未尝试过此方法...)
这避免了每次需要进行地图查找或更新时创建新对象的开销。和普通的isEqualKeys
操作应为O(1)...就像常规的Map
。
并且,如果您准备接受他们所做的实现选择,则Apache Commons HashMap
会为您定制/专门化CaseInsensitiveMap
。
但是如果可以接受O(logN)CaseInsensitiveMap
和AbstractHashedMap
操作,则可以选择不区分大小写的字符串比较器的get
;例如使用put
。
并且如果您不介意每次执行TreeMap
或String.CASE_INSENSITIVE_ORDER
时都创建一个新的临时String对象,那么Vishal的回答就很好。 (尽管如此,如果您这样做,您将不会保留键的原始大小写...)
子类String.CASE_INSENSITIVE_ORDER
,并创建一个将put
和get
上的键小写的版本(可能还有其他面向键的方法)。
或将HashMap
组合到新类中,并将所有内容委托给地图,但转换键。
如果需要保留原始密钥,则可以维护双重映射,也可以将原始密钥与值一起存储。
我想到两个选择:
put
用作get
的键。HashMap
与自定义s.toUpperCase().hashCode();
一起使用,而忽略大小写。否则,如果您更喜欢解决方案,而不是定义一种新的String,我宁愿使用所需的区分大小写功能实现一个新的Map。
为了记住hashCode,“包装” String会更好。在普通的String类中,hashCode()第一次是O(N),然后是O(1),因为它被保留以备将来使用。
Map
这将允许您使用Java中Hashtable的任何实现,并具有O(1)hasCode()。
您可以使用TreeMap<String>
中基于Comparator
的public class HashWrap {
private final String value;
private final int hash;
public String get() {
return value;
}
public HashWrap(String value) {
this.value = value;
String lc = value.toLowerCase();
this.hash = lc.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof HashWrap) {
HashWrap that = (HashWrap) o;
return value.equalsIgnoreCase(that.value);
} else {
return false;
}
}
@Override
public int hashCode() {
return this.hash;
}
//might want to implement compare too if you want to use with SortedMaps/Sets.
}
HashingStrategy
注意:我是Eclipse Collections的撰稿人。
基于其他答案,基本上有两种方法:子类化Map
或包装Eclipse Collections。第一个需要更多的工作。实际上,如果要正确执行此操作,则必须重写几乎所有方法(HashingStrategy<String> hashingStrategy =
HashingStrategies.fromFunction(String::toUpperCase);
MutableMap<String, String> node = HashingStrategyMaps.mutable.of(hashingStrategy);
)。
反正有问题。如果要避免将来出现问题,则必须在HashMap
大小写操作中指定String
。因此,您将创建新的方法(containsKey, entrySet, get, put, putAll and remove
,...)。一切都更加容易和清晰地包装字符串:
Locale
而且,关于您对性能的担心:过早的优化是万恶之源:)
有关健壮的CaseInsensitiveMap / CaseInsensitiveSet实现,请查看java-util(String
)。
这些地图在标准O(1)查找时间内执行,保留添加项的大小写,支持所有Map API,例如putAll(),retainAll(),removeAll(),并允许将异构项放入密钥集中。
此外,.keySet()和.entrySet()返回的java.util.Set区分大小写(许多实现不区分大小写)。最后,如果在迭代时从键/条目集中获取键,则会返回一个String,而不是CaseInsensitiveString包装器类。