在Java中使用长列表作为映射键

在Java中使用长列表作为映射键,java,list,design-patterns,collections,map,Java,List,Design Patterns,Collections,Map,我计划使用一个映射,其中键是相当长的列表(约10/100k的小元素): Map myMap=newhashmap(); 默认的List::hashCode()实现(在AbstractList中)使用循环中所有列表元素的hashCode计算其hashCode值。另外,List::equals()方法依次比较所有列表元素,并为第一个不同的元素返回false 这一切都是有意义的,除了列表hashcode值没有被缓存(jdk6),因此每次都会重新计算,这使得这种使用模式非常低效(map经常依赖hash

我计划使用一个映射,其中键是相当长的列表(约10/100k的小元素):

Map myMap=newhashmap();
默认的
List::hashCode()
实现(在AbstractList中)使用循环中所有列表元素的hashCode计算其hashCode值。另外,
List::equals()
方法依次比较所有列表元素,并为第一个不同的元素返回false

这一切都是有意义的,除了列表hashcode值没有被缓存(jdk6),因此每次都会重新计算,这使得这种使用模式非常低效(map经常依赖hashcode)。
equals()
的问题会少一些,因为不同的元素平均会有一个索引较低的第一个不同项,因此循环会提前中断(但必须比较相同列表中的所有元素)

我想用一个新的自定义
KeyList
类封装我的列表,将hashcode值保留在缓存中以提高性能,但是:

  • 这不是小事,因为您必须处理同步问题并实现一些列表接口方法
  • 它具有侵入性,因为您必须在客户端代码中使用此装饰程序
  • 这并不能解决比较相同元素时的
    equals()
    性能问题

  • 处理这种情况是否有更好的方法?

    在这种情况下,列表必须是不可变的,否则
    hashCode()
    会随着时间的推移而改变,从而损坏哈希映射。如果列表是不可变的,则可以计算一次
    hashCode()
    ,并将其用作
    Long
    对象中包装的键

    如果您坚持使用列表界面作为键,您应该实现您提到的
    KeyList
    。只需创建一个列表实现,该实现委托给原始列表,但重写hashCode(),以返回可在构造函数中初始化的记忆值

    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 */
    }
    
    public抽象静态类memorizedHashCodeList实现列表{
    私有最终长哈希码;
    非公开最终名单代表;
    公共MemorizedHashCodeList(列表委托){
    this.delegate=委托;
    hashCode=delegate.hashCode();
    }
    /*清单的其余部分*/
    }
    
    为了加快实现速度,可以使用Google Guava的
    ForwardingList
    类为您实现委托模式


    但最重要的是,确保你的列表是不变的。不要试图通过在可变列表上进行同步来破坏代码,这是行不通的。

    我认为最好的解决方案是使用KeyList类。我建议将键列表类隔离到一个方法中,并让客户机代码从该方法请求列表引用。在其他任何地方,它都可以作为列表引用

    假设键列表是不可变的(它们需要用作映射键),则只需在计算哈希代码时进行同步。或者甚至跳过它,复制字符串创建其哈希代码的方式


    除了只有32位的标准哈希代码之外,您还可以添加一个更强大的哈希代码,该哈希代码具有非常低的冲突概率,并对其进行比较,而不是对列表进行逐个元素的比较。使用您自己的KeyList类可以覆盖equals和hashCode。

    我认为您需要重构代码并创建一个单独的对象用作键。您几乎可以做任何事情来创建一个唯一的键,可以将列表保留在一个数组中并使用数组索引,或者计算列表的哈希代码并将其用作键。但将大型对象保留为键是非常罕见的,我建议您不要这样做。

    我的列表确实是不可变的。如果我使用list hashcode值作为键,冲突的可能性太高(仅32位),因此我不能使用您的建议。即使使用大的校验和,我也不能冒发生冲突的风险。如果在任何地方都重用相同的列表对象,那么使用IdentityMap而不是HashMap。
    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 */
    }