Как обновить значение, учитывая ключ в hashmap?

625

Предположим, у нас есть HashMap<String, Integer>в Java.

Как обновить (увеличить) целочисленное значение строкового ключа для каждого существования найденной строки?

Можно было бы удалить и повторно ввести пару, но накладные расходы были бы проблемой.
Другим способом было бы просто поставить новую пару и заменить старую.

В последнем случае, что произойдет, если возникнет коллизия хеш-кода с новым ключом, который я пытаюсь вставить? Правильное поведение для хеш-таблицы будет состоять в том, чтобы назначить для нее другое место или составить список из нее в текущем сегменте.

laertis
источник

Ответы:

973
map.put(key, map.get(key) + 1);

все должно быть в порядке. Это обновит значение для существующего сопоставления. Обратите внимание, что это использует автобокс. С помощью map.get(key)мы получаем значение соответствующего ключа, после чего вы можете обновить его по вашему требованию. Здесь я обновляюсь, чтобы увеличить значение на 1.

Мэтью Флэшен
источник
21
На самом деле это самое надежное и масштабируемое корпоративное решение.
Лавир Уайолет
12
@Lavir, это не плохое решение, но не вижу, как это самое надежное и масштабируемое. Атомный интегратор вместо этого гораздо более масштабируем.
Джон Винт
13
это предполагает, что ключ существует, верно? Я получаю исключение nullPointer, когда это не так.
Ян
84
В Java 8 этого можно легко избежать getOrDefault, например, используя:map.put(key, count.getOrDefault(key, 0) + 1);
Martin
2
@Martin .. map.put (ключ, map.getOrDefault (ключ, 0) + 1)
суббота
112

Java 8 способ:

Вы можете использовать computeIfPresentметод и предоставить ему функцию отображения, которая будет вызываться для вычисления нового значения на основе существующего.

Например,

Map<String, Integer> words = new HashMap<>();
words.put("hello", 3);
words.put("world", 4);
words.computeIfPresent("hello", (k, v) -> v + 1);
System.out.println(words.get("hello"));

В качестве альтернативы вы можете использовать mergeметод, где 1 - это значение по умолчанию, а функция увеличивает существующее значение на 1:

words.merge("hello", 1, Integer::sum);

Кроме того, существует множество других полезных методов, таких , как putIfAbsent, getOrDefault, forEach, и т.д.

damluar
источник
3
Я только что проверил ваши решения. Второй, со ссылкой на метод, работает. Первое, лямбда-выражение одно, не работает согласованно, когда любое значение вашей карты null(скажем words.put("hello", null);), результат все еще nullне так, 1как я ожидал.
Тао Чжан
4
От Javadoc: «Если значение для указанного ключа присутствует и не равно нулю, попытается вычислить новое отображение». Вы можете использовать compute()вместо этого, он также будет обрабатывать nullзначения.
Дамлуар
Я хочу увеличить свое значение на 1. .mergeЭто мое решение с Integer::sum.
S_K
48
hashmap.put(key, hashmap.get(key) + 1);

Метод putбудет заменить значение существующего ключа и создаст его , если не существует.

oracleruiz
источник
55
Нет, это не создает, это дает nullPointer Exception.
smttsp
13
Код является правильным ответом на данный вопрос, но был опубликован через год после того, как точно такой же код был опубликован в принятом ответе. То, что отличает этот ответ, состоит в том, что put может создать новую запись, но не в этом примере. Если вы используете hashmap.get (key) для несуществующего ключа / значения, вы получите нулевое значение, а при попытке увеличения, как говорит @smttsp, это будет NPE. -1
Зак
8
Этот ответ неверен. NullPointerException для несуществующих ключей
Элеонора
@smttp NullpointterException, только если вы не инициализировали значение (как вы знаете, вы не можете увеличить ноль)
Mehdi
Дублирование и неправильное объяснение ... и создаст его, если его не существует. Вы не можете сделать null + 1это, так как это попытается распаковать nullв целое число, чтобы сделать приращение.
AxelH
43

Упрощенный способ Java 8 :

map.put(key, map.getOrDefault(key, 0) + 1);

При этом используется метод HashMap, который извлекает значение для ключа, но если ключ не может быть получен, он возвращает указанное значение по умолчанию (в данном случае «0»).

Это поддерживается в ядре Java: HashMap <K, V> getOrDefault (ключ объекта, V defaultValue)

Кристофер Булл
источник
3
Это лучше, если вы используете Java 1.8
Hemant
30

Замените Integerна AtomicIntegerи вызовите один из методов incrementAndGet/ getAndIncrement.

Альтернатива заключается в том, чтобы обернуть intв свой собственный MutableIntegerкласс, у которого есть increment()метод, вам остается только решить проблему безопасности потоков.

BalusC
источник
37
AtomicInteger является изменяемым целым числом, но встроенным. Я серьезно сомневаюсь, что написание собственного MutableInteger - лучшая идея.
Питер Лори
Кастом MutableIntegerлучше, так как AtomicIntegerиспользует volatile, у которого есть накладные расходы. Я бы использовал int[1]вместо MutableInteger.
Олив
@Oliv: не одновременно.
BalusC
@BalusC, но все же, изменчивая запись дороже. Это делает недействительными кэши. Если бы не было разницы, все переменные были бы непостоянными.
Олив
@Oliv: в вопросе явно упоминается коллизия хеш-кода, поэтому параллелизм важен для OP.
BalusC
19

Одноканальное решение:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);
Punktum
источник
4
Это не добавляет ничего нового к существующим ответам, не так ли?
Роберт
1
Да, это так. Правильный помеченный ответ вызовет исключение NullPointerException, если ключ не существует. Это решение будет работать нормально.
Хемант
18

Решение @ Мэтью является самым простым и в большинстве случаев будет работать достаточно хорошо.

Если вам нужна высокая производительность, AtomicInteger - более подходящее решение, например @BalusC.

Однако более быстрое решение (при условии, что безопасность потоков не является проблемой) заключается в использовании TObjectIntHashMap, который предоставляет метод приращения (ключ) и использует примитивы и меньшее количество объектов, чем при создании AtomicIntegers. например

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");
Питер Лори
источник
13

Вы можете увеличивать, как показано ниже, но вам нужно проверить существование, чтобы исключение NullPointerException

if(!map.containsKey(key)) {
 p.put(key,1);
}
else {
 p.put(key, map.getKey()+1);
}
Isuru
источник
9

Существует ли хэш (со значением 0) или он «помещен» на карту с первым шагом? Если он «помещен» в первый шаг, код должен выглядеть так:

if (hashmap.containsKey(key)) {
    hashmap.put(key, hashmap.get(key)+1);
} else { 
    hashmap.put(key,1);
}
sudoBen
источник
7

Может быть немного поздно, но вот мои два цента.

Если вы используете Java 8, вы можете использовать метод computeIfPresent . Если значение для указанного ключа присутствует и не равно нулю, то оно пытается вычислить новое сопоставление, учитывая ключ и его текущее сопоставленное значение.

final Map<String,Integer> map1 = new HashMap<>();
map1.put("A",0);
map1.put("B",0);
map1.computeIfPresent("B",(k,v)->v+1);  //[A=0, B=1]

Мы также можем использовать другой метод putIfAbsent для установки ключа. Если указанный ключ еще не связан со значением (или сопоставлен со значением NULL), этот метод связывает его с данным значением и возвращает значение NULL, иначе возвращается текущее значение.

В случае если карта является общей для всех потоков, мы можем использовать ConcurrentHashMapи AtomicInteger . Из документа:

An AtomicIntegerявляется значением int, которое может быть обновлено атомарно. AtomicInteger используется в приложениях, таких как счетчики с атомным приращением, и не может использоваться в качестве замены для Integer. Тем не менее, этот класс расширяет Number, чтобы обеспечить единообразный доступ инструментам и утилитам, которые работают с классами на основе чисел.

Мы можем использовать их как показано:

final Map<String,AtomicInteger> map2 = new ConcurrentHashMap<>();
map2.putIfAbsent("A",new AtomicInteger(0));
map2.putIfAbsent("B",new AtomicInteger(0)); //[A=0, B=0]
map2.get("B").incrementAndGet();    //[A=0, B=1]

Следует обратить внимание на то, что мы вызываем getзначение ключа Bи затем вызываем incrementAndGet()его значение, которое, конечно же, является ключом AtomicInteger. Мы можем оптимизировать его, так как метод putIfAbsentвозвращает значение ключа, если оно уже присутствует:

map2.putIfAbsent("B",new AtomicInteger(0)).incrementAndGet();//[A=0, B=2]

Кроме того, если мы планируем использовать AtomicLong, то в соответствии с документацией в условиях высокой конкуренции ожидаемая пропускная способность LongAdder значительно выше за счет более высокого потребления пространства. Также проверьте этот вопрос .

akhil_mittal
источник
5

Более чистое решение без NullPointerException:

map.replace(key, map.get(key) + 1);
Сергей Дирин
источник
5
если ключ не существует, то map.get (ключ) сгенерирует NPE
Navi
Да, это правда,
Сергей Дирин,
2

Поскольку я не могу комментировать несколько ответов из-за меньшей репутации, я опубликую решение, которое я применил.

for(String key : someArray)
{
   if(hashMap.containsKey(key)//will check if a particular key exist or not 
   {
      hashMap.put(hashMap.get(key),value+1);// increment the value by 1 to an already existing key
   }
   else
   {
      hashMap.put(key,value);// make a new entry into the hashmap
   }
}
Ааюш Нигам
источник
1

Используйте forцикл для увеличения индекса:

for (int i =0; i<5; i++){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("beer", 100);

    int beer = map.get("beer")+i;
    System.out.println("beer " + beer);
    System.out ....

}
VANHOUTTE
источник
3
Это будет просто перезаписывать карту на каждой итерации. Смотрите ответ Мэтью для правильного подхода.
Ли
1

Здесь есть вводящие в заблуждение ответы на этот вопрос, которые подразумевают, что метод put Hashtable заменит существующее значение, если ключ существует, это не верно для Hashtable, а скорее для HashMap. См. Javadoc для HashMap http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html#put%28K,%20V%29

user1048218
источник
1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

или

Integer i = map.get(key);
map.put(key, i == null ? newValue : i + 1);

Целочисленные значения - это примитивные типы данных http://cs.fit.edu/~ryan/java/language/java-data.html , поэтому вам нужно вынуть их, выполнить некоторый процесс, а затем вернуть его обратно. если у вас есть значение, которое не является типом данных Primitive, вам нужно только извлечь его, обработать, не нужно возвращать его обратно в хэш-карту.

Кридз Жень
источник
1
Спасибо за этот фрагмент кода, который может оказать некоторую немедленную помощь. Правильное объяснение значительно повысило бы его образовательную ценность, показав, почему это хорошее решение проблемы, и сделало бы его более полезным для будущих читателей с похожими, но не идентичными вопросами. Пожалуйста, отредактируйте свой ответ, чтобы добавить объяснение и указать, какие ограничения и предположения применяются.
Тоби Спейт
0

Пытаться:

HashMap hm=new HashMap<String ,Double >();

НОТА:

String->give the new value; //THIS IS THE KEY
else
Double->pass new value; //THIS IS THE VALUE

Вы можете изменить либо ключ, либо значение в вашей хэш-карте, но вы не можете изменить оба одновременно.

NARAYANAN.M
источник
0

Используйте Java8 встроенную функцию 'computeIfPresent'

Пример:

public class ExampleToUpdateMapValue {

    public static void main(String[] args) {
        Map<String,String> bookAuthors = new TreeMap<>();
        bookAuthors.put("Genesis","Moses");
        bookAuthors.put("Joshua","Joshua");
        bookAuthors.put("Judges","Samuel");

        System.out.println("---------------------Before----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
        // To update the existing value using Java 8
        bookAuthors.computeIfPresent("Judges", (k,v) -> v = "Samuel/Nathan/Gad");

        System.out.println("---------------------After----------------------");
        bookAuthors.entrySet().stream().forEach(System.out::println);
    }
}
Раджеш Д
источник