Як оновити значення, надавши ключ у хешмапі?


624

Припустимо, у нас є HashMap<String, Integer>Java.

Як я оновлюю (збільшуючи) ціле число значення рядкового ключа для кожного існування рядка, який я знаходжу?

Можна було б вилучити пару і повернути пару, але накладні витрати будуть турботою.
Іншим способом було б просто поставити нову пару і стару замінити.

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

Відповіді:


972
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);
Мартін

2
@Martin .. map.put (ключ, map.getOrDefault (клавіша, 0) + 1)
Sathesh

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, і т.д.


3
Я просто перевірив ваші рішення. Другий, той, з посиланням на метод, працює. Перший, лямбда-вираз, не працює послідовно, коли якесь значення вашої карти є null(скажімо words.put("hello", null);), результат все ще nullне такий, 1як я очікував.
Дао Чжан

4
Від Javadoc: "Якщо значення для вказаного ключа присутнє і ненулеве, спробуємо обчислити нове відображення". Ви можете використовувати compute()замість цього, він також буде обробляти nullзначення.
damluar

Я хочу збільшити своє значення на 1. .merge- це моє рішення Integer::sum.
S_K

48
hashmap.put(key, hashmap.get(key) + 1);

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


55
Ні, він не створює, він дає nullPointer Exception.
smttsp

13
Код є правильною відповіддю на дане запитання, але був розміщений через рік після того, як саме той же код був розміщений у прийнятій відповіді. Те, що відрізняє цю відповідь, - це те, що вона ставить, може створити новий запис, який він може, але не в цьому прикладі. Якщо ви використовуєте hashmap.get (ключ) для неіснуючого ключа / значення, ви отримаєте недійсне значення, а при спробі збільшення - як @smttsp каже, що це буде NPE. -1
Зак

8
Ця відповідь неправильна. NullPointerException для неіснуючих ключів
Eleanore

@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 (клавіша Object, V defaultValue)


3
Це краще, якщо ви знаходитесь на java 1.8
Hemant Nagpal

30

Замініть Integerна AtomicIntegerі зателефонуйте одному із методів incrementAndGet/ getAndIncrement.

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


37
AtomicInteger є змінним цілим числом, але вбудованим. Я серйозно сумніваюся, що написати власний MutableInteger - це краща ідея.
Пітер Лорі

Користувальницьке MutableIntegerкраще, як AtomicIntegerвикористання volatile, яке має накладні витрати. Я б використовував int[1]замість цього MutableInteger.
Олів

@Oliv: не паралельно.
BalusC

@BalusC, але все-таки, летючі записи дорожчі. Це визнає недійсними кеші. Якби не було різниці, всі змінні були б мінливими.
Олів

@Oliv: питання прямо зазначає зіткнення хеш-коду, тому паралельність важлива для ОП.
BalusC

19

Рішення з однієї лінії:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);

4
Це не додає нічого нового до існуючих відповідей, чи не так?
Роберт

1
Так. Якщо правильна позначена відповідь викине NullPointerException, якщо ключ не існує. Це рішення буде добре працювати.
Hemant Nagpal

18

@ Рішення Метью найпростіше і в більшості випадків буде працювати досить добре.

Якщо вам потрібна висока продуктивність, AtomicInteger - це краще рішення ala @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);
}

9

Чи існує хеш (зі значенням 0) чи він "поставлений" на карту на перший приріст? Якщо він "поставлений" на перший приріст, код повинен виглядати так:

if (hashmap.containsKey(key)) {
    hashmap.put(key, hashmap.get(key)+1);
} else { 
    hashmap.put(key,1);
}

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, щоб поставити ключ. Якщо вказаний ключ вже не асоційований із значенням (або відображається на нульовому значенні), цей метод пов'язує його із заданим значенням та повертає нульове значення, інше повертає поточне значення.

Якщо карта поділяється на нитки, ми можемо скористатись ConcurrentHashMapі AtomicInteger . Від док.

AtomicIntegerЄ INT значення , яке може оновлюватися атомарному. AtomicInteger використовується в таких додатках, як атомно-примножений лічильник, і його не можна використовувати як заміну цілого числа. Однак цей клас поширює номер, щоб дозволити рівномірний доступ до інструментів та утиліт, які працюють з класами на основі чисельності.

Ми можемо використовувати їх як показано:

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 значно вище, за рахунок більшого споживання місця. Також перевірте це питання .



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 ....

}

3
Це просто перезаписує Карту на кожній ітерації. Дивіться відповідь Метью щодо правильного підходу.
Лі


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 , тому потрібно вийняти його, зробити якийсь процес, а потім повернути його назад. якщо у вас є значення, яке не є примітивними типами даних, вам потрібно лише вийняти його, обробити його, не потрібно повертати його в хешмап.


1
Дякуємо за цей фрагмент коду, який може надати негайну допомогу. Правильне пояснення значно покращило б її навчальну цінність, показавши, чому це хороше рішення проблеми, і зробило б її кориснішою для майбутніх читачів із подібними, але не однаковими питаннями. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
Toby Speight

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

Ви можете змінити або ключ, або значення у вашій хешмапі, але ви не можете змінити обидва одночасно.


0

Використовуйте вбудований в fuction 'computeIfPresent' Java8

Приклад:

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