Соображения производительности для keySet() и entrySet() карты

Все,

Может кто-нибудь, пожалуйста, дайте мне знать, какие проблемы производительности между двумя? Сайт: CodeRanch содержит краткий обзор внутренних вызовов, которые необходимы при использовании keySet() и get(). Но было бы здорово, если бы кто-нибудь мог предоставить точную информацию о потоке, когда используются методы keySet() и get(). Это поможет мне лучше понять проблемы производительности.

Ответы

Ответ 1

Прежде всего, это полностью зависит от того, какой тип Карты вы используете. Но поскольку поток JavaRanch говорит о HashMap, я буду считать, что это реализация, о которой вы говорите. И давайте предположим, что вы говорите о стандартной реализации API от Sun/Oracle.

Во-вторых, если вы беспокоитесь о производительности при повторении своей хэш-карты, я предлагаю вам взглянуть на 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...
    }
    // ...
}

и a 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()

Здесь вам сначала нужно получить набор ключей. Это занимает время, пропорциональное емкости карты (в отличие от размера для LinkedHashMap). После этого вы вызываете get() один раз для каждой клавиши. Конечно, в среднем случае с хорошей реализацией hashCode это занимает постоянное время. Тем не менее, это неизбежно потребует много вызовов .hashCode и .equals, что, очевидно, займет больше времени, чем просто вызов entry.value().

Ответ 2

Наиболее распространенным случаем, когда использование entrySet является предпочтительным по сравнению с keySet, является то, что вы выполняете итерацию через все пары ключ/значение на карте.

Это более эффективно:

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

чем:

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

Поскольку во втором случае для каждого ключа в keySet вызывается метод map.get(), который - в случае с HashMap - требует, чтобы методы hashCode() и equals() ключевого объекта были оценены в чтобы найти связанное значение *. В первом случае исключена дополнительная работа.

Изменить: это еще хуже, если вы рассматриваете TreeMap, где вызов get - это O (log2 (n)), то есть для сравнения для воли может потребоваться запустить log2 (n) раз (n = размер карты), прежде чем найти соответствующее значение.

* Некоторые реализации карты имеют внутренние оптимизации, которые проверяют идентификацию объектов перед вызовами hashCode() и equals().

Ответ 3

Вот ссылка на статью, сравнивающую производительность entrySet(), keySet() и valueSet(), и советы относительно того, когда использовать каждый подход.

По-видимому, использование keySet() выполняется быстрее (помимо удобства), чем entrySet(), пока вам не нужно Map.get() значения.