4

Map インスタンスを返すさまざまなカスタム (ソース コードは利用できません) フレームワークによって少しずつ細かな作業が行われています。残念ながら、これらのフレームワークは、Collections.unmodifiableMap でラップされた Map インスタンスを返す点で一貫性がありません。私のコードで高度な不変性を (より簡単にマルチスレッドで使用できるように) 保証するために、これらのフレームワークによって返されるものに対して 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*コレクションにはこの問題はありません。それ自体が anでImmutableList.copyOf(list)ある a で が呼び出された場合、引数自体が返されます。さらに、それらをインターフェイスではなく型として参照 (およびチェック)できるため、不変のインスタンスがあるかどうかを簡単に知ることができます。したがって、1 つのオプションは、フレームワークからこれらの不変コレクションに結果をコピーし、それらを独自のコード全体で使用することです。(それらには真に不変であるという利点もあります...変更不可能なラッパーは、何かがそれへの参照を持っている場合、ラップする元の可変インスタンス自体を変更することを可能にします。)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;
}
    ...

このクラスは、実際のマップの薄いラッパーに過ぎないことがわかります。などのすべてのメソッドgetSizeisEmptyおよびマップの状態に影響を与えないその他のメソッドは、ラップされたマップ インスタンスに委任されます。マップの状態に影響を与える他のメソッド ( putremove) はスローするだけUnsupportedOperationExceptionなので、パフォーマンスの過負荷はほとんどありません。

于 2010-11-08T21:10:35.327 に答える
2

すべてのフィードバックを確認した後、私が何をしようとも、解決策は何らかの形のクラッジ (マイルドな臭い) になるという結論に達しました。これは、変更不可能なインスタンスを生成する Collections API の一部が、変更不可能なインスタンスのネストを回避することを提供していなかったり、クライアントがネストを適切に回避するための「パブリック」な方法を提供していなかったりしたためだと思います。

また、複数のクラス ローダーと RMI を介したシリアル化に関する考慮事項により、私が本当に気に入った 1 つのソリューション (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 コンテキスト内で発生、クラス名の文字列が適切にインターンされていると仮定すると、参照比較のすべての利点があります。そして、カプセル化を丁寧に保ちながらそれを行います(クラス名を直接参照するコードを避けます)。ただし、2 つの仮定が成り立たない場合、評価は標準の文字列比較に戻ります。これは、ライブラリの異なるバージョン間でクラス名が変更されていないと仮定して機能します (確率はかなり低いようです)。

このアプローチで私が忘れていることや欠けていることはありますか?

また、フィードバックをお寄せいただきありがとうございます。ほんとうにありがとう。

于 2010-11-09T16:58:18.147 に答える
2

(C) についての私の考えは、ホットスポット コンパイラは、別のメソッド呼び出しの結果を返すだけの小さなメソッドをインライン化するのにかなり優れているはずだということです。しかし、いくつかの UnmodifiableMaps にラップされた HashMap など、いくつかのレベルの間接性を見抜くことができるとは思えません。また、ライブラリが変更不可能なマップを返す場合があることを知っていれば、時期尚早の最適化になるとは思いません。このような Collections.unmodifiableMap の独自のラッパーを作成してみませんか (テストされていません。また、Collections の Impl は非公開であるため、単純な instanceof が機能しないことに気付きました):

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 に答える
0

A)いいえ

B) いいえ

C) そうかもしれませんが、それは JVM の実装に依存すると思います。

問題のデコレータは非常に軽量ですが、単に別のメソッド呼び出しを行う余分なメソッド呼び出しが環境でパフォーマンスの問題を引き起こす特定の理由はありますか? これは、標準的なアプリケーションでは問題にならないはずです。そのため、本当に特別なアプリケーション/環境 (CPU を集中的に使用する、リアルタイム、モバイル デバイスなど) を使用しない限り、問題になることはありません。

于 2010-11-08T20:59:58.497 に答える
0

一般に、最初のルールは、本当に必要でない限り最適化しないことです。基本的に、アプリケーションからパフォーマンス データを収集して、ラップされたコレクションがパフォーマンス ヒットを引き起こしているかどうかを確認する必要があります。

ただし、コレクションが変更不可能かどうかを本当に確認したい場合は、「UnmodifiableMap」.equals(Class.getSimpleName()) を確認できます。

于 2010-11-08T21:01:50.950 に答える