【问题标题】:Using long lists as map keys in Java在 Java 中使用长列表作为映射键
【发布时间】:2013-09-28 06:31:01
【问题描述】:

我打算使用一个键是相当长的列表(约 10/100k 的小元素)的地图:

Map<List<K>, V> myMap = new HashMap<List<K>, V>();

默认的List::hashCode() 实现(在 AbstractList 中)使用所有列表元素的哈希码在循环中计算它的哈希码值。 List::equals() 方法也依次比较所有列表元素,并为第一个不同的元素返回 false。

这一切都是有道理的,除了列表哈希码值没有缓存(JDK 6)因此每次都重新计算,这使得这种使用模式非常低效(地图经常依赖哈希码) . equals() 的问题会更少,因为不同的元素平均会在相当低的索引处具有第一个不同的项目,因此循环会提前中断(但必须比较相同列表的所有元素)。

我正在考虑用一个新的自定义 KeyList 类封装我的列表,将哈希码值保存在缓存中以提高性能,但是:

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

有没有更好的办法来处理这种情况?

【问题讨论】:

    标签: java list design-patterns collections map


    【解决方案1】:

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

    如果你坚持使用List接口作为key,你应该实现你提到的KeyList。只需创建一个委托给原始 List 的 List 实现,但覆盖 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 */
    }
    

    为了加快实现速度,您可以使用 Google Guava 的 ForwardingList 类为您实现委托模式。

    但最重要的是,请确保您的列表是不可变的。不要试图用可变列表上的同步来破坏你的代码,它是行不通的。

    【讨论】:

    • 我的列表确实是不可变的。如果我使用列表哈希码值作为键,冲突的可能性太高(仅限 32 位),所以我不能使用你的建议。即使有很大的校验和,我也不能冒险遇到碰撞。
    • 如果你到处重复使用相同的 List 对象,那么使用 IdentitiyMap 而不是 HashMap。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      假设键列表是不可变的,它们需要用作 Map 键,您只需要在计算哈希码时进行同步。或者甚至跳过它,并复制 String 创建其哈希码的方式。

      除了只有 32 位的标准哈希码之外,您也许可以添加一个冲突概率非常低的更强大的哈希码,然后对它们进行比较,而不是对列表进行逐个元素的比较。使用您自己的 KeyList 类允许覆盖等号以及 hashCode。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-02-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-27
        • 1970-01-01
        相关资源
        最近更新 更多