Міркування щодо продуктивності для keySet () та entrySet () Map


76

Всі,

Хто-небудь, будь ласка, дайте мені знати, які саме проблеми з продуктивністю між двома? Сайт: CodeRanch надає короткий огляд внутрішніх викликів, необхідних при використанні keySet () та get (). Але було б чудово, якщо б хтось міг надати точні подробиці про потік, коли використовуються методи keySet () і get (). Це допомогло б мені краще зрозуміти проблеми з продуктивністю.

Відповіді:


70

Перш за все, це повністю залежить від того, який тип карти ви використовуєте. Але оскільки потік 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...
    }
    // ...
}

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

    // ...
}

Отже, у вас все ... Це код, який диктує, що станеться, коли ви повторите вхід через SetSet. Він проходить по всьому масиву, стільки ж, скільки ємність карт.

HashMap.keySet () та .get ()

Тут спочатку потрібно взяти набір ключів. Це вимагає часу, пропорційного місткості карти (на відміну від розміру для LinkedHashMap). Після цього ви телефонуєте get()один раз для кожної клавіші. Звичайно, у середньому випадку при хорошій реалізації hashCode це вимагає постійного часу. Однак це неминуче вимагатиме багато .hashCodeта .equalsдзвінків, що, очевидно, займе більше часу, ніж просто entry.value()дзвінок.


1
+1 "Ітерація над колекційними переглядами LinkedHashMap вимагає часу, пропорційного розміру карти, незалежно від її місткості. Ітерація над HashMap, швидше за все, буде дорожчою, вимагаючи часу, пропорційного її потужності."
metdos

Але якщо вам просто потрібно отримати доступ до ключів або просто потрібно отримати доступ до значень Map, тоді віддайте перевагу ітерації над Set, поверненим keySet () і Collection return values ​​(). Ще один момент, Набір, повернутий keySet () та Колекція, повернутий значеннями (), підтримуються оригінальною картою. Тобто, якщо ви внесете в них будь-які зміни, вони будуть відображені назад на Карті, однак, обидва вони не підтримують методи add () та addAll (), тобто ви не можете додати новий ключ до Set або нове значення у Збірнику.
sactiw

@aioobe ЯК ви писали: "Це код, який диктує, що станеться, коли ви будете перебирати entrySet. Він проходить через весь масив, який дорівнює місткості карти". Чи не повинно бути "..... що дорівнює розміру карти"?
Суміт Кумар Саха

Хороша хороша відповідь. Я завжди волію посилатися на вихідний код, оскільки він є остаточним джерелом істини
ACV

75

Найпоширеніший випадок, коли використання 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, де дзвінок для отримання дорівнює O (log2 (n)), тобто для порівняння для will може знадобитися запустити log2 (n) разів (n = розмір карти) перед тим, як знайти відповідне значення.

* Деякі реалізації Map мають внутрішню оптимізацію, яка перевіряє ідентичність об'єктів перед hashCode()і equals()викликається.


3
Крім того, якщо карта є TreeMap, а не HashMap, get()це O(log(n))операція.
ILMTitan

@ILMIian та Michael: Чому різниця між TreeMap та HashMap?
name_masked

TreeMap та HashMap - це різні структури даних, TreeMap базується на дереві Red / Black. HashMap - це хеш-таблиця сегмента та списку. В обох випадках виклик get () не є безкоштовним, і його вартість залежить від типу структури даних.
Michael Barker

1
Java 8 (і вище) має значення в HashMapреалізованому як двійкове дерево пошуку замість LinkedList. Дивіться openjdk.java.net/jeps/180
Користувач-початківець

14

Ось посилання на статтю, в якій порівнюється ефективність та entrySet(), keySet()і values(), і поради щодо того, коли використовувати кожен підхід.

Очевидно, використання keySet()швидше (крім того, що є більш зручним), ніж до entrySet()тих пір, поки вам не потрібні Map.get()значення.


1
У цій статті ви сказали: "Цей метод (використовуючи keySet або значення замість entrySet) дає невелику перевагу в продуктивності перед ітерацією entrySet (приблизно на 10% швидше) і є більш чистим". Чи можу я знати, як ви отримали таке значення "10%"? Ви не показали жодного вимірювання та жодних зовнішніх даних, що містять таке значення.
dantuch

@dantuch Я не Сергій, але стаття має сенс ІМХО. Однак стаття застаріла з 2008 року. Ви завжди можете створити мікроблянку за допомогою штангенциркуля Google, наприклад, для останньої версії JDK, якщо вам цікаво, опублікуйте результати.
Stefan L
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.