传递 Collections.unmodifying* 一个已经用 Collections.unmodifying* 包装的实例有多低效?

发布于 2024-10-02 00:44:05 字数 1556 浏览 8 评论 0原文

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

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.unmodifyingMap 向其传递一个已经由框架通过同一调用包装的实例。我的重新包装可能会导致整个实例发生额外的方法调用。

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

问题:

    A) Does the Collections.unmodifiableMap() method check to see if it was passed an instance of UnmodifiableMap, and if so just return the same reference (thereby avoiding the need to check prior to calling the method)?
    B) In order to proactively avoid receiving modification exceptions, is there a way to query a Map instance (other than using reflection) to detect if it is mutable (or immutable)?
    C) If the answer to A is no, then is there some efficiencies in the JVM/HotSpot which eliminate the overhead of calling through the multiple method hops to get to the core instance?

I have bits of piecework being done by different custom (source code unavailable) frameworks which hand back Map instances. Unfortunately, these frameworks are not consistent in their returning Map instances which have been wrapped with Collections.unmodifiableMap. To ensure a higher degree of immutability (for easier multi-threaded use) in my code, I have just uniformly called Collections.unmodifiableMap on anything returned by these frameworks.

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);
}

I realized that I might have a performance issue around this part of my design. And that in some instances, I was calling Collections.unmodifiableMap passing it an instance which had already been wrapped by the framework by the same call. And that my re-wrapping was likely causing an extra method call across the entire instance.

It appears that using "instanceof Collections.UnmodifiableMap" doesn't work. And I cannot find any way to detect (excluding using reflection which is not an option in this situation - WAY too slow) if the Map instance I am currently referencing needs to be wrapped or not.

Questions:

    A) Does the Collections.unmodifiableMap() method check to see if it was passed an instance of UnmodifiableMap, and if so just return the same reference (thereby avoiding the need to check prior to calling the method)?
    B) In order to proactively avoid receiving modification exceptions, is there a way to query a Map instance (other than using reflection) to detect if it is mutable (or immutable)?
    C) If the answer to A is no, then is there some efficiencies in the JVM/HotSpot which eliminate the overhead of calling through the multiple method hops to get to the core instance?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

萌化 2024-10-09 00:44:05

据我所知:

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

Guava 的< code>Immutable* 集合没有这个问题。如果使用本身就是 ImmutableListlist 调用 ImmutableList.copyOf(list),则返回参数本身。此外,您可以将它们称为(并使用 instanceof 检查)Immutable* 类型而不是接口,从而轻松了解您是否有不可变实例或不是。因此,一种选择是将框架中的结果复制到这些不可变集合中,并在您自己的代码中使用它们。 (它们还具有真正不可变的优点......不可修改的包装器允许它们包装的原始可变实例在有引用的情况下自行改变。)

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

To the best of my knowledge:

  • A) No.
  • B) No.
  • C) No.

Guava's Immutable* collections don't have this problem. If ImmutableList.copyOf(list) is called with a list that is itself an ImmutableList, the argument itself is returned. Additionally, you can refer to them as (and check with instanceof for) the Immutable* type rather than the interface, making it easy to know if you have an immutable instance or not. So one option is to copy the results from the framework into these immutable collections and use those throughout your own code. (They also have the advantage of being truly immutable... unmodifiable wrappers allow the original mutable instance that they wrap to be mutated itself if something has a reference to it.)

All that said, I wouldn't worry too much about the possible overhead of passing a method call through 1 or 2 unmodifiable wrapper layers, as long as you're not going to somehow wrap them again and again. As others have pointed out, it's highly unlikely that you'll ever notice a performance issue because of this.

衣神在巴黎 2024-10-09 00:44:05

当您将一个不可修改的映射包装到另一个映射中时,您不必担心性能。看一下 UnmodifyingMap 类:

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;
}
    ...

您可以看到该类只是真实地图的一个薄包装。 getSizeisEmpty 等所有方法以及其他不影响地图状态的方法都将委托给包装的地图实例。其他影响地图状态的方法(putremove)只是抛出UnsupportedOperationException,因此性能过载几乎为零。

You don't have to worry about performance when you wrap one unmodifiable map into another. Have a look at UnmodifiableMap class:

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;
}
    ...

You can see that this class is only a thin wrapper around the real map. All methods like getSize, isEmpty and other methods that don't affect map's state are delegated to the wrapped map instance. Other methods that affect map's state (put, remove) just throw UnsupportedOperationException So there is almost zero performance overload.

夏至、离别 2024-10-09 00:44:05

我对 (C) 的想法是,热点编译器应该非常擅长内联仅返回另一个方法调用结果的小方法。但我怀疑它是否可以穿透多个间接级别,例如包装在多个 UnmodifyingMap 中的 HashMap。我也不认为如果您知道您的库有时会返回不可修改的地图,那么这将是不成熟的优化。为什么不像这样为 Collections.unmodifyingMap 创建自己的包装器(未经测试,我只是注意到简单的 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);
    }
}

My thoughts on (C) are that the hotspot compiler should be pretty good at inlining small methods that just return the result of another method call. But I doubt that it can see through several levels of indirection, for example a HashMap wrapped in several UnmodifiableMaps. I also don't think it would be premature optimization if you know your library is sometimes returning Unmodifiable Maps. Why not create your own wrapper for Collections.unmodifiableMap like this (untested, and I just noticed that a simple instanceof won't work since the Impl in Collections is private):

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);
    }
}
风铃鹿 2024-10-09 00:44:05

在查看了所有反馈后,我得出的结论是,无论我做什么,解决方案都将是某种形式的拼凑(有轻微的气味)。我认为这是因为 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);
    }
}

这仍然具有引用比较的所有优点假设一切都发生在同一个类加载器上下文中并且类名的字符串已经正确实习过。它在做到这一点的同时礼貌地保持封装(避免我的代码直接引用类名)。但是,如果这两个假设不成立,则评估将回退到标准字符串比较,假设类名在库的不同版本之间不会更改(这似乎概率相当低),则该比较将起作用。

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

再次感谢大家的反馈。我真的很感激。

After reviewing all of the feedback, I came to the conclusion that no matter what I do, the solution is going to be some form of kludge (have a mild odor). I think this is due to the fact that the part of the Collections API which produces unmodifiable instances didn't provide for avoiding nesting unmodifiable instances nor did it provide a "public" way for a client to properly avoid the nesting.

And due to considerations around multiple class loaders and serialization via RMI, the one solution I really liked (class reference comparison by Jorn Horstmann) has issues. However, when I take his approach and combine it with a modification of the class name approach (recommneded by Eugene Kuleshov), I think I get as close as I am going to get to having a solution that will help me in my multi-threaded distributed processing environment. And it goes a little bit like this:

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);
    }
}

This will still has all the advantages of a reference comparison assuming everything is happening within the same ClassLoader context and the classname's string has been properly interned. And it does it while politely keeping encapsulation (avoiding my code referencing the class name directly). However, if the two assumptions don't hold, then the evaluation will fall back to a standard string comparison which will work assuming the class name does not change between different versions of the library (which seems to have a pretty low probability).

Is there anything I am forgetting or missing in this approach?

And thank you again, everyone, for your feedback. I really appreciate it.

裸钻 2024-10-09 00:44:05

A) 否

B) 否

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

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

A) No

B) No

C) Possibly, but I would expect that to depend on the JVM implmentation.

The decorator in question is extremely light weight, is there some specific reason that an extra method call that simply makes another method call would cause some performance issues in your environment? This should not be an issue in any standard application, so unless you have some really special application/environment (cpu intensive, realtime, mobile device...) it shouldn't be a problem.

倾城月光淡如水﹏ 2024-10-09 00:44:05

一般来说,第一条规则是除非确实有必要,否则不要对其进行优化。基本上,您需要从应用程序中收集一些性能数据,以查看包装的集合是否会导致任何性能影响。

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

Generally, the first rule is to not optimize it unless it is really necessary. Basically you need to collect some performance data from your application to see if wrapped collection cause any performance hit.

But if you really want to check if collection is unmodifiable, you can check "UnmodifiableMap".equals(Class.getSimpleName())

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文