web-dev-qa-db-ja.com

MapのkeySet()およびentrySet()のパフォーマンスに関する考慮事項

すべて、

誰もが2の間のパフォーマンスの問題を正確に教えてもらえますか?サイト: CodeRanch は、keySet()およびget()を使用するときに必要になる内部呼び出しの概要を提供します。ただし、keySet()メソッドとget()メソッドを使用するときに、フローに関する正確な詳細を誰でも提供できれば素晴らしいことです。これは、パフォーマンスの問題をよりよく理解するのに役立ちます。

73
name_masked

まず、これは使用しているマップのタイプに完全に依存します。しかし、JavaRanchスレッドはHashMapについて話しているため、それがあなたが参照している実装であると想定します。また、Sun/Oracleの標準API実装について話していると仮定しましょう。

第二に、ハッシュマップを反復処理する際のパフォーマンスが心配な場合は、 LinkedHashMap をご覧になることをお勧めします。ドキュメントから:

LinkedHashMapのコレクションビューの反復には、容量に関係なく、マップのサイズに比例した時間が必要です。 HashMapでの反復はより高価である可能性が高く、その容量に比例した時間が必要です。

HashMap.entrySet()

この実装のソースコードが利用可能です。実装は基本的に新しい_HashMap.EntrySet_を返すだけです。次のようなクラス:

_private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public Iterator<Map.Entry<K,V>> iterator() {
        return newEntryIterator(); // returns a HashIterator...
    }
    // ...
}
_

HashIteratorは次のようになります

_private abstract class HashIterator<E> implements Iterator<E> {
    Entry<K,V> next;    // next entry to return
    int expectedModCount;   // For fast-fail
    int index;      // current slot
    Entry<K,V> current; // current entry

    HashIterator() {
        expectedModCount = modCount;
        if (size > 0) { // advance to first entry
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
    }

    final Entry<K,V> nextEntry() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        Entry<K,V> e = next;
        if (e == null)
            throw new NoSuchElementException();

        if ((next = e.next) == null) {
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
    current = e;
        return e;
    }

    // ...
}
_

だから、あなたはそれを持っています...それはentrySetを反復するときに何が起こるかを指示するコードです。マップの容量と同じ長さのアレイ全体をウォークスルーします。

HashMap.keySet()および.get()

ここでは、最初にキーのセットを取得する必要があります。これには、マップのcapacityに比例する時間がかかります(LinkedHashMapのsizeとは対照的)。これが完了したら、キーごとにget()を1回呼び出します。確かに、平均的なケースでは、適切なhashCode実装では、これには一定の時間がかかります。ただし、必然的に多くの_.hashCode_および_.equals_呼び出しが必要になります。これは、明らかにentry.value()呼び出しを行うよりも時間がかかります。

69
aioobe

EntrySetの使用がkeySetよりも望ましい最も一般的なケースは、Map内のすべてのキー/値ペアを反復処理する場合です。

これはより効率的です。

for (Map.Entry entry : map.entrySet()) {
    Object key = entry.getKey();
    Object value = entry.getValue();
}

より:

for (Object key : map.keySet()) {
    Object value = map.get(key);
}

2番目のケースでは、keySetのすべてのキーに対してmap.get()メソッドが呼び出されます。これは、HashMapの場合、hashCode()およびequals()メソッドを必要とします関連付けられた値を見つけるためにキーオブジェクトが評価されます*。最初のケースでは、余分な作業がなくなります。

編集:これは、getの呼び出しがO(log2(n))であるTreeMapを検討する場合、さらに悪いです、つまり、コンパレータはlog2(n)回(n =マップのサイズ)を見つける前に実行する必要があります関連する値。

*一部のマップ実装には、hashCode()およびequals()が呼び出される前にオブジェクトのIDをチェックする内部最適化があります。

69
Michael Barker

記事へのリンクですentrySet()keySet()values()のパフォーマンスの比較、および各アプローチをいつ使用するかに関するアドバイス。

keySet()の使用は、値をentrySet()する必要がない限り、Map.get()よりも高速です(便利であることに加えて)。

14
Stefan L