我计划使用一个地图,其中键是相当长的列表(~10 / 100k的小元素):
Map<List<K>, V> myMap = new HashMap<List<K>, V>();
默认的List::hashCode()
实现(在AbstractList中)使用循环中所有列表元素的哈希码计算它的哈希码值。此外,List::equals()
方法依次比较所有列表元素,并对不同的第一个元素返回false。
除了列表哈希码值没有缓存(JDK 6)并因此每次重新计算,这都会产生意义,这使得这种使用模式效率非常低(地图经常依赖于哈希码) 。 equals()
的问题会更少,因为不同的元素在相当低的索引处平均会有第一个不同的项,因此循环会提前中断(但必须比较相同列表的所有元素)。
我正在考虑使用新的自定义KeyList
类封装我的列表,将哈希值保留在缓存中以提高性能,但是:
equals()
性能问题。处理这种情况会有更好的想法吗?
答案 0 :(得分:6)
对于这种情况,您的列表必须是 immutable ,否则hashCode()
会随着时间而改变,这会破坏哈希映射。如果列表是不可变的,则可以计算hashCode()
一次,并将其用作Long
对象中包含的密钥。
如果您坚持使用List接口作为密钥,则应实现您提到的KeyList
。只需创建一个委托给原始List的List实现,但是重写hashCode()以返回可以在构造函数中初始化的memoized值。
public abstract static class MemoizedHashCodeList<K> implements List<K>{
private final long hashCode;
private final List<K> delegate;
public MemoizedHashCodeList(List<K> delegate) {
this.delegate = delegate;
hashCode = delegate.hashCode();
}
/* Rest of the List<K> implementation */
}
为了加快实施速度,您可以使用Google Guava的ForwardingList
类来为您实现委派模式。
但最重要的是,请确保您的列表是不可变的。不要试图在可变列表上同步破坏你的代码,它只是不起作用。
答案 1 :(得分:2)
我认为您最好的解决方案是KeyList类。我建议将密钥列表的类别隔离到一个方法,并让客户端代码从该方法请求List引用。在其他任何地方,它都可以作为列表引用。
假设密钥列表是不可变的,它们需要用作Map键,您只需要在计算哈希码时进行同步。或者甚至跳过它,并复制String创建其哈希码的方式。
除了标准的哈希码(只有32位)之外,您还可以添加一个具有极低冲突概率的更强大的哈希值,并比较这些哈希值而不是对列表进行逐元素比较。使用您自己的KeyList类可以覆盖equals,以及hashCode。
答案 2 :(得分:0)
我认为您需要重构代码并创建一个单独的对象用作键。你可以做几乎任何事情来创建一个唯一的密钥,也许将列表保存在一个数组中并使用数组索引,或者计算列表的哈希码并使用keep作为密钥。但是将大对象保留为键非常不寻常,我建议你不要这样做。