Wie aktualisiere ich einen Wert anhand eines Schlüssels in einer Hashmap?

624

Angenommen, wir haben eine HashMap<String, Integer>in Java.

Wie aktualisiere (inkrementiere) ich den Integer-Wert des String-Schlüssels für jede Existenz des gefundenen Strings?

Man könnte das Paar entfernen und erneut eingeben, aber der Aufwand wäre ein Problem.
Ein anderer Weg wäre, einfach das neue Paar zu setzen und das alte zu ersetzen.

Was passiert im letzteren Fall, wenn eine Hashcode-Kollision mit einem neuen Schlüssel auftritt, den ich einzufügen versuche? Das richtige Verhalten für eine Hashtabelle wäre, ihr einen anderen Ort zuzuweisen oder eine Liste daraus im aktuellen Bucket zu erstellen.

laertis
quelle

Antworten:

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

sollte gut sein. Der Wert für das vorhandene Mapping wird aktualisiert. Beachten Sie, dass dies Auto-Boxing verwendet. Mit Hilfe von erhalten map.get(key)wir den Wert des entsprechenden Schlüssels, dann können Sie mit Ihrer Anforderung aktualisieren. Hier aktualisiere ich auf den Inkrementwert um 1.

Matthew Flaschen
quelle
21
In der Tat ist dies die robusteste und skalierbarste Unternehmenslösung.
Lavir der Whiolet
12
@Lavir, es ist keine schlechte Lösung, aber ich sehe nicht, wie es am robustesten und skalierbarsten ist. Eine atomare Ganzzahl ist stattdessen viel skalierbarer.
John Vint
13
Dies setzt voraus, dass der Schlüssel existiert, oder? Ich erhalte eine nullPointer-Ausnahme, wenn dies nicht der Fall ist.
Ian
84
Mit Java 8 kann dies leicht vermieden werden, indem getOrDefaultzum Beispiel verwendet wird:map.put(key, count.getOrDefault(key, 0) + 1);
Martin
2
@ Martin .. map.put (Schlüssel, map.getOrDefault (Schlüssel, 0) + 1)
Sathesh
112

Java 8 Weg:

Sie können die computeIfPresentMethode verwenden und ihr eine Zuordnungsfunktion zuweisen, die aufgerufen wird, um einen neuen Wert basierend auf dem vorhandenen zu berechnen.

Zum Beispiel,

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"));

Alternativ können Sie eine mergeMethode verwenden, bei der 1 der Standardwert ist und die Funktion den vorhandenen Wert um 1 erhöht:

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

Darüber hinaus gibt es eine Reihe von anderen nützlichen Methoden, wie putIfAbsent, getOrDefault, forEachusw.

Damluar
quelle
3
Ich habe gerade Ihre Lösungen getestet. Der zweite, der mit Methodenreferenz, funktioniert. Der erste, der Lambda-Ausdruck, funktioniert nicht konsistent, wenn ein Wert Ihrer Karte null(sagen wir words.put("hello", null);) ist. Das Ergebnis ist immer noch nullnicht so, 1wie ich es erwarten würde.
Tao Zhang
4
Von Javadoc: "Wenn der Wert für den angegebenen Schlüssel vorhanden und nicht null ist, wird versucht, eine neue Zuordnung zu berechnen." Sie können compute()stattdessen auch nullWerte verarbeiten.
Damluar
Ich möchte meinen Wert um 1 erhöhen. Ist .mergemeine Lösung mit Integer::sum.
S_K
48
hashmap.put(key, hashmap.get(key) + 1);

Das Verfahren putwird ersetzen Sie den Wert eines vorhandenen Schlüssels und wird es schaffen , wenn nicht vorhanden ist .

oracleruiz
quelle
55
Nein, es schafft nicht, es gibt nullPointer Exception.
smttsp
13
Der Code ist eine korrekte Antwort auf die gegebene Frage, wurde jedoch ein Jahr nach der Veröffentlichung des exakt gleichen Codes in der akzeptierten Antwort veröffentlicht. Die Sache, die diese Antwort unterscheidet, ist die Angabe, dass put einen neuen Eintrag erstellen kann, was möglich ist, aber nicht in diesem Beispiel. Wenn Sie hashmap.get (Schlüssel) für einen nicht vorhandenen Schlüssel / Wert verwenden, erhalten Sie null, und wenn Sie versuchen, ein Inkrement durchzuführen, wie @smttsp sagt, wird es NPE. -1
Zach
8
Diese Antwort ist falsch. NullPointerException für nicht vorhandene Schlüssel
Eleanore
@smttp NullpointterException nur, wenn Sie den Wert nicht initialisiert haben (wie Sie wissen, können Sie null nicht erhöhen)
Mehdi
Vervielfältigung und falsche Erklärung ... und wird erstellt, wenn sie nicht vorhanden ist. null + 1Dies ist nicht möglich, da hierdurch versucht wird, die nullin eine Ganzzahl zu entpacken , um das Inkrement durchzuführen .
AxelH
43

Der vereinfachte Java 8- Weg:

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

Dies verwendet die Methode von HashMap, die den Wert für einen Schlüssel abruft. Wenn der Schlüssel jedoch nicht abgerufen werden kann, wird der angegebene Standardwert zurückgegeben (in diesem Fall eine '0').

Dies wird in Java unterstützt: HashMap <K, V> getOrDefault (Objektschlüssel, V defaultValue)

Christopher Bull
quelle
3
Dies ist eine ist besser für den Fall, dass Sie auf Java 1.8
Hemant Nagpal
30

Ersetzen Sie Integerdurch AtomicIntegerund rufen Sie eine der incrementAndGet/ getAndIncrementMethoden auf.

Eine Alternative besteht darin, eine intin Ihre eigene MutableIntegerKlasse increment()einzubinden, die eine Methode hat. Sie müssen nur noch ein Sicherheitsbedenken lösen.

BalusC
quelle
37
AtomicInteger ist eine veränderbare Ganzzahl, aber integriert. Ich bezweifle ernsthaft, dass es eine bessere Idee ist, eine eigene MutableInteger zu schreiben.
Peter Lawrey
Benutzerdefiniert MutableIntegerist besser als AtomicIntegerVerwendungen volatile, die Overhead haben. Ich würde int[1]anstelle von verwenden MutableInteger.
Oliv
@Oliv: nicht gleichzeitig.
BalusC
@BalusC aber dennoch ist flüchtiges Schreiben teurer. Es macht Caches ungültig. Wenn es keinen Unterschied gäbe, wären alle Variablen volatil.
Oliv
@Oliv: Die Frage erwähnt explizit die Hashcode-Kollision, daher ist die Parallelität für OP wichtig.
BalusC
19

Einzeilige Lösung:

map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);
Punktum
quelle
4
Das fügt den vorhandenen Antworten nichts Neues hinzu, oder?
Robert
1
Ja tut es. Die korrekte markierte Antwort löst eine NullPointerException aus, wenn der Schlüssel nicht vorhanden ist. Diese Lösung wird gut funktionieren.
Hemant Nagpal
18

@ Matthews Lösung ist die einfachste und funktioniert in den meisten Fällen gut genug.

Wenn Sie eine hohe Leistung benötigen, ist AtomicInteger eine bessere Lösung als @BalusC.

Eine schnellere Lösung (vorausgesetzt, die Thread-Sicherheit ist kein Problem) ist die Verwendung von TObjectIntHashMap, die eine Inkrementierungsmethode (Schlüsselmethode) bereitstellt und Grundelemente und weniger Objekte verwendet als das Erstellen von AtomicIntegern. z.B

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");
Peter Lawrey
quelle
13

Sie können wie unten inkrementieren, müssen jedoch die Existenz überprüfen, damit keine NullPointerException ausgelöst wird

if(!map.containsKey(key)) {
 p.put(key,1);
}
else {
 p.put(key, map.getKey()+1);
}
Isuru
quelle
9

Existiert der Hash (mit 0 als Wert) oder wird er beim ersten Inkrement auf die Karte "gesetzt"? Wenn es beim ersten Inkrement "gesetzt" wird, sollte der Code folgendermaßen aussehen:

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

Es mag etwas spät sein, aber hier sind meine zwei Cent.

Wenn Sie Java 8 verwenden, können Sie die computeIfPresent- Methode verwenden. Wenn der Wert für den angegebenen Schlüssel vorhanden und nicht null ist, wird versucht, eine neue Zuordnung unter Berücksichtigung des Schlüssels und seines aktuellen zugeordneten Werts zu berechnen.

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]

Wir können auch eine andere Methode putIfAbsent verwenden , um einen Schlüssel zu setzen. Wenn der angegebene Schlüssel noch keinem Wert zugeordnet ist (oder null zugeordnet ist), ordnet diese Methode ihn dem angegebenen Wert zu und gibt null zurück, andernfalls wird der aktuelle Wert zurückgegeben.

Falls die Karte über Threads gemeinsam genutzt , dann können wir nutzen ConcurrentHashMapund Atomicinteger . Aus dem Dokument:

An AtomicIntegerist ein int-Wert, der atomar aktualisiert werden kann. Eine AtomicInteger wird in Anwendungen wie z. B. atomar inkrementierten Zählern verwendet und kann nicht als Ersatz für eine Ganzzahl verwendet werden. Diese Klasse erweitert jedoch Number, um einen einheitlichen Zugriff für Tools und Dienstprogramme zu ermöglichen, die sich mit numerisch basierten Klassen befassen.

Wir können sie wie gezeigt verwenden:

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]

Ein zu beachtender Punkt ist, dass wir aufrufen get, um den Wert für den Schlüssel zu erhalten, Bund dann incrementAndGet()dessen Wert aufrufen , was natürlich der Fall ist AtomicInteger. Wir können es optimieren, da die Methode putIfAbsentden Wert für den Schlüssel zurückgibt, falls dieser bereits vorhanden ist:

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

Nebenbei bemerkt, wenn wir vorhaben, AtomicLong zu verwenden, ist der erwartete Durchsatz von LongAdder gemäß der Dokumentation unter hohen Konflikten auf Kosten eines höheren Platzverbrauchs erheblich höher. Überprüfen Sie auch diese Frage .

akhil_mittal
quelle
5

Die sauberere Lösung ohne NullPointerException lautet:

map.replace(key, map.get(key) + 1);
Sergey Dirin
quelle
5
Wenn der Schlüssel nicht existiert, wirft map.get (Schlüssel) NPE
Navi
Ja das ist wahr
Sergey Dirin
2

Da ich aufgrund des geringeren Ansehens einige Antworten nicht kommentieren kann, werde ich eine Lösung veröffentlichen, die ich angewendet habe.

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
   }
}
Aayush Nigam
quelle
1

Verwenden Sie eine forSchleife, um den Index zu erhöhen:

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
quelle
3
Das würde die Karte bei jeder Iteration einfach überschreiben. Siehe Matthews Antwort für den richtigen Ansatz.
Leigh
1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

oder

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

Ganzzahl ist der primitive Datentyp http://cs.fit.edu/~ryan/java/language/java-data.html . Sie müssen ihn also herausnehmen, einen Prozess ausführen und dann zurücksetzen. Wenn Sie einen Wert haben, der kein primitiver Datentyp ist, müssen Sie ihn nur herausnehmen, verarbeiten und nicht wieder in die Hashmap einfügen.

Kreedz Zhen
quelle
1
Vielen Dank für dieses Code-Snippet, das möglicherweise sofortige Hilfe bietet. Eine richtige Erklärung würde den Bildungswert erheblich verbessern , indem sie zeigt, warum dies eine gute Lösung für das Problem ist, und sie für zukünftige Leser mit ähnlichen, aber nicht identischen Fragen nützlicher machen. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, und geben Sie an, welche Einschränkungen und Annahmen gelten.
Toby Speight
0

Versuchen:

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

HINWEIS:

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

Sie können entweder den Schlüssel oder den Wert in Ihrer Hashmap ändern, aber Sie können nicht beide gleichzeitig ändern.

NARAYANAN.M
quelle
0

Verwenden Sie die in Java8 integrierte Funktion 'computeIfPresent'.

Beispiel:

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);
    }
}
Rajesh D.
quelle