4

我有一些由不同的自定义(源代码不可用)框架完成的部分工作,这些框架交还 Map 实例。不幸的是,这些框架在返回的 Map 实例中并不一致,这些实例已经用 Collections.unmodifiableMap 包装。为了确保我的代码具有更高程度的不变性(以便于多线程使用),我刚刚对这些框架返回的任何内容统一调用 Collections.unmodifiableMap。

Map<String, Record> immutableMap = framework.getRecordsByName();
//does this created a nested set of unmodifiableMap wrapper instances?
this.immutableField = Collections.unmodifiableMap(immutableMap);
.
.
.
Map<String, Record> maybeImmutableMap = framework.getRecordsByName();
//is there some means to get instanceof to work?
if (!(maybeImmutableMap instanceof Collections.UnmodifiableMap))
{
    this.immutableField = Collections.unmodifiableMap(maybeImmutableMap);
}

我意识到我的这部分设计可能存在性能问题。在某些情况下,我调用 Collections.unmodifiableMap 向它传递一个实例,该实例已经被框架通过同一个调用包装。而且我的重新包装可能会导致整个实例的额外方法调用。

似乎使用“instanceof Collections.UnmodifiableMap”不起作用。如果我当前引用的 Map 实例需要被包装,我找不到任何方法来检测(不包括使用反射,这在这种情况下不是一个选项 - 太慢了)。

问题:

    A) Collections.unmodifiableMap() 方法是否检查它是否传递了 UnmodifiableMap 的实例,如果是,则返回相同的引用(从而避免在调用该方法之前需要检查)?
    B)为了主动避免接收到修改异常,有没有办法查询一个Map实例(除了使用反射)来检测它是否可变(或不可变)?
    C) 如果 A 的答案是否定的,那么 JVM/HotSpot 中是否有一些效率可以消除通过多个方法跃点调用以到达核心实例的开销?
4

6 回答 6

5

尽我所知:

  • A) 没有。
  • B) 没有。
  • C) 没有。

GuavaImmutable*收藏没有这个问题。如果使用本身为ImmutableList.copyOf(list)a 的 a 调用,则返回参数本身。此外,您可以将它们称为(并检查)类型而不是接口,从而很容易知道您是否有一个不可变的实例。因此,一种选择是将框架中的结果复制到这些不可变集合中,并在您自己的代码中使用它们。(它们还具有真正不可变的优点......不可修改的包装器允许它们包装的原始可变实例自身发生变异,如果有东西引用它。)listImmutableListinstanceofImmutable*

综上所述,我不会太担心通过 1 或 2 个不可修改的包装层传递方法调用的可能开销,只要您不打算以某种方式一次又一次地包装它们。正如其他人所指出的那样,您极不可能因此而注意到性能问题。

于 2010-11-08T21:07:24.910 回答
5

当您将一个不可修改的地图包装到另一个地图时,您不必担心性能。看看UnmodifiableMap类:

private static class UnmodifiableMap<K,V> implements Map<K,V>, Serializable {
    ...
UnmodifiableMap(Map<? extends K, ? extends V> m) {
        if (m==null)
            throw new NullPointerException();
        this.m = m;
    }

public int size()                {return m.size();}
    ...
public V put(K key, V value) {
    throw new UnsupportedOperationException();
    }
public V remove(Object key) {
    throw new UnsupportedOperationException();
    }

public Set<K> keySet() {
    if (keySet==null)
    keySet = unmodifiableSet(m.keySet());
    return keySet;
}

public Set<Map.Entry<K,V>> entrySet() {
    if (entrySet==null)
    entrySet = new UnmodifiableEntrySet<K,V>(m.entrySet());
    return entrySet;
}
    ...

你可以看到这个类只是真实地图的一个薄包装。所有像getSize,isEmpty和其他不影响地图状态的方法都委托给包装的地图实例。其他影响 map 状态的方法(put, remove)只是 throw ,UnsupportedOperationException所以性能过载几乎为零。

于 2010-11-08T21:10:35.327 回答
2

我对 (C) 的想法是,热点编译器应该非常擅长内联只返回另一个方法调用结果的小方法。但我怀疑它是否可以看穿多个间接级别,例如包装在几个 UnmodifiableMaps 中的 HashMap。如果您知道您的库有时会返回不可修改的地图,我也不认为这是过早的优化。为什么不像这样为 Collections.unmodifiableMap 创建自己的包装器(未经测试,我只是注意到一个简单的 instanceof 不起作用,因为 Collections 中的 Impl 是私有的):

public class MyCollections {
    private static final Class UNMODIFIABLE_MAP_CLASS = Collections.unmodifiableMap(new HashMap()).getClass();

    public static <K, V> Map<K, V> unmodifiableMap(Map<K, V> map) {
        return map.getClass() == UNMODIFIABLE_MAP_CLASS ? map : Collections.unmodifiableMap(map);
    }
}
于 2010-11-08T23:43:26.263 回答
2

在查看了所有反馈后,我得出的结论是,无论我做什么,解决方案都将是某种形式的杂物(有轻微的气味)。我认为这是因为产生不可修改实例的 Collections API 部分没有提供避免嵌套不可修改实例,也没有为客户端提供正确避免嵌套的“公共”方式。

由于对多个类加载器和通过 RMI 进行序列化的考虑,我真正喜欢的一个解决方案(Jorn Horstmann 的类引用比较)存在问题。但是,当我采用他的方法并将其与类名方法的修改(由 Eugene Kuleshov 推荐)相结合时,我想我将尽可能接近找到一个可以帮助我处理多线程的解决方案分布式处理环境。它有点像这样:

public class MyCollections {
    private static final String UNMODIFIABLE_MAP_CLASS_NAME =
        Collections.unmodifiableMap(new HashMap()).getClass().getName();

    public static <K, V> Map<K, V> unmodifiableMap(Map<K, V> map) {
        return map.getClass().getName().equals(UNMODIFIABLE_MAP_CLASS_NAME)
                 ? map
                 : Collections.unmodifiableMap(map);
    }
}

假设一切都发生在同一个 ClassLoader 上下文中并且类名的字符串已被正确地插入,这仍然具有引用比较的所有优点。它在礼貌地保持封装的同时做到这一点(避免我的代码直接引用类名)。但是,如果这两个假设不成立,那么评估将退回到标准字符串比较,假设类名在库的不同版本之间没有变化(这似乎概率很低),该比较将起作用。

这种方法有什么我忘记或遗漏的吗?

再次感谢大家的反馈。对此,我真的非常感激。

于 2010-11-09T16:58:18.147 回答
0

A) 没有

B) 没有

C)可能,但我希望这取决于 JVM 实现。

有问题的装饰器重量极轻,是否有某些特定原因导致简单地进行另一个方法调用的额外方法调用会在您的环境中导致一些性能问题?这在任何标准应用程序中都不应该是问题,因此除非您有一些非常特殊的应用程序/环境(cpu 密集型、实时、移动设备......),否则它应该不是问题。

于 2010-11-08T20:59:58.497 回答
0

一般来说,第一条规则是除非真的有必要,否则不要优化它。基本上,您需要从应用程序中收集一些性能数据,以查看包装的收集是否会导致任何性能损失。

但是如果你真的想检查集合是否不可修改,你可以检查 "UnmodifiableMap".equals(Class.getSimpleName())

于 2010-11-08T21:01:50.950 回答