Ключова перевірка існування в HashMap


309

Чи завжди необхідна перевірка наявності ключа в HashMap?

У мене є HashMap з 1000 заявками, і я дивлюсь на підвищення ефективності. Якщо доступ до HashMap доступний дуже часто, то перевірка наявності ключа при кожному доступі призведе до великих витрат. Натомість, якщо ключа немає, і, отже, відбувається виняток, я можу вилучити виняток. (коли я знаю, що це станеться рідко). Це зменшить доступ до HashMap вдвічі.

Це може бути не дуже корисною програмою, але це допоможе мені зменшити кількість звернень. Або я щось тут пропускаю?

[ Оновлення ] У мене немає нульових значень у HashMap.


8
"отже, відбувається виняток" - який виняток? Це не буде від java.util.HashMap ...
serg10,

Відповіді:


513

Чи зберігаєте ви коли-небудь нульове значення? Якщо ні, ви можете просто зробити:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // No such key
}

В іншому випадку ви можете просто перевірити наявність, якщо ви отримаєте нульове значення:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // Key might be present...
    if (map.containsKey(key)) {
       // Okay, there's a key but the value is null
    } else {
       // Definitely no such key
    }
}

1
@Samuel: Тільки коли null - це можливе значення. Якщо у вас точно немає нульових значень на карті, це просто getдобре, і ви уникаєте робити два огляди, коли вам також потрібно це значення.
Джон Скіт

Хоча це, мабуть, зрозуміліше як приклад, ви також можете написати if(value!=null || map.containsKey(key))для другої частини. Принаймні, якщо ви хочете зробити те саме в будь-якому випадку - без повторного коду. Він працюватиме через коротке замикання .
Cullub

66

Ви нічого не отримаєте, перевіривши, чи існує ключ. Це код HashMap:

@Override
public boolean containsKey(Object key) {
    Entry<K, V> m = getEntry(key);
    return m != null;
}

@Override
public V get(Object key) {
    Entry<K, V> m = getEntry(key);
    if (m != null) {
        return m.value;
    }
    return null;
}

Просто перевірте , якщо повертається значення для get()відрізняв null.

Це вихідний код HashMap.


Ресурси:


2
Який сенс показувати одну конкретну реалізацію цих методів?
jarnbjo

2
Щоб пояснити, що в більшості випадків перевірка наявності ключа займе приблизно стільки ж часу, ніж отримання значення. Таким чином, він не оптимізує нічого для перевірки наявного ключа, перш ніж отримати значення. Я знаю, що це узагальнення, але це може допомогти зрозуміти.
Колін Геберт

Хорошим посиланням є grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… (OpenJDK дуже сильно походить від коду Sun), і, здається, я помиляюся. Я порівнював версію для Java5 з Java6; вони працюють по-різному в цій області (але обидва є правильними, як і фрагменти, які ви опублікували).
Дональні стипендіати

2
Як зазначається у прийнятій відповіді, ця закуска очевидно неправильна. Звичайно, ви щось отримуєте, перевіряючи наявність ключових значень чи порівняння значень - ви можете відмежувати неіснуючі ключі від ключових, які існують, але відображаються на нульове значення.
Йоганнес Х.

43

Кращим способом є використання containsKeyметоду HashMap. Завтра хтось додасть нулю до Карти. Ви повинні розрізняти наявність ключа і ключ має нульове значення.


Так. Або підкласируйте HashMap, щоб взагалі не зберігати null.
RobAu

1
1+ для примітивних типів, як непотрібне значення, не потрібно використовувати цю відповідь
Прашант Бханаркар

Також більш вільно писати .containsKey (), ніж перевіряти на null. Нам слід більше турбуватися про те, що легко читати, що економить час розробників, ніж про незначну оптимізацію в більшості випадків. Принаймні не оптимізуйте, перш ніж це стане необхідним.
Макс

23

Ви маєте на увазі, що у вас є подібний код

if(map.containsKey(key)) doSomethingWith(map.get(key))

повсюдно ? Тоді вам слід просто перевірити, чи map.get(key)повернуто нулеве, і все. До речі, HashMap не викидає винятки за відсутніми ключами, а замість цього повертає null. Єдиний випадок, коли containsKeyце потрібно, коли ви зберігаєте нульові значення, щоб розрізняти нульове значення і відсутнє значення, але це зазвичай вважається поганою практикою.


8

Просто використовуйте containsKey()для наочності. Це швидко і зберігає код чистим і читабельним. Вся справа HashMapз в тому , що ключ пошуку швидко, просто переконайтеся , що hashCode()і equals()правильно реалізовані.



3

Ви також можете використовувати computeIfAbsent()метод уHashMap класі.

У наступному прикладі mapзберігається перелік транзакцій (цілих чисел), які застосовуються до ключа (назва банківського рахунку). Для того, щоб додати 2 угоди 100і 200до checking_accountви можете написати:

HashMap<String, ArrayList<Integer>> map = new HashMap<>();
map.computeIfAbsent("checking_account", key -> new ArrayList<>())
   .add(100)
   .add(200);

Таким чином, вам не потрібно перевіряти, чи checking_accountіснує ключ чи ні.

  • Якщо його не існує, він буде створений і повернутий вираз лямбда.
  • Якщо воно існує, то значення для ключа буде повернено computeIfAbsent().

Дійсно елегантний! 👍


0

Я зазвичай використовую ідіому

Object value = map.get(key);
if (value == null) {
    value = createValue(key);
    map.put(key, value);
}

Це означає, що ви потрапляєте на карту лише два рази, якщо ключ відсутній


0
  1. Якщо ключовим класом є ваш, переконайтеся, що реалізовані методи hashCode () та equals ().
  2. В основному, доступ до HashMap повинен бути O (1), але при неправильній реалізації методу hashCode він стає O (n), оскільки значення з тим самим хеш-ключем зберігатиметься як список пов'язаних.

0

Відповідь Джон Скіт ефективно вирішує два сценарії (відображення зі nullзначенням, а не nullзначенням) ефективно.

Щодо кількості записів та ступеня ефективності, я хотів би щось додати.

У мене є HashMap з заявою 1.000 записів, і я дивлюсь на підвищення ефективності. Якщо доступ до HashMap доступний дуже часто, то перевірка наявності ключа при кожному доступі призведе до великих витрат.

Карта з 1.000 записами - це не величезна карта.
А також карта з 5.000 або 10.000 записами.
Mapпризначені для швидкого пошуку з такими розмірами.

Тепер він передбачає, що hashCode()клавіша карт забезпечує хороший розподіл.

Якщо ви можете використовувати Integerяк ключовий тип, зробіть це.
Його hashCode()метод є дуже ефективним, оскільки зіткнення неможливі для унікальнихint значень:

public final class Integer extends Number implements Comparable<Integer> {
    ...
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    public static int hashCode(int value) {
        return value;
    }
    ...
}

Якщо для ключа, ви повинні використовувати інший вбудований тип, Stringнаприклад, який часто використовується вMap вас можуть виникнути зіткнення, але від 1 тис. До декількох тисяч об'єктів, у Mapвас має бути дуже мало його як String.hashCode()методу забезпечує хороший розподіл.

Якщо ви користуєтеся власним типом, замініть hashCode()та equals()правильно та переконайтесь, що загальний рівень hashCode()забезпечує справедливий розподіл.
Ви можете звернутися до пункту 9 посилань на Java Effectiveнього.
Ось публікація, яка детально описує спосіб.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.