Bir hashmap içinde bir anahtar verilen bir değer nasıl güncellenir?


624

Varsayalım HashMap<String, Integer>Java'da.

Bulduğum dizenin her varlığı için dize anahtarının tamsayı değerini nasıl güncelleyebilirim (arttırabilirim)?

Birisi çifti çıkarıp tekrar girebilirdi, ama genel gider bir endişe olurdu.
Başka bir yol sadece yeni çifti koymak ve eski çifti değiştirmektir.

İkinci durumda, eklemeye çalıştığım yeni bir anahtarla bir hashcode çarpışması olursa ne olur? Bir hashtable için doğru davranış, ona farklı bir yer atamak veya geçerli grupta listeden bir liste yapmak olacaktır.

Yanıtlar:


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

iyi olmalı. Mevcut eşlemenin değerini güncelleyecektir. Bunun otomatik boks kullandığını unutmayın. Yardımı ile map.get(key)karşılık gelen anahtarın değerini almak, o zaman gereksinimi ile güncelleyebilirsiniz. Burada değeri 1 arttırmak için güncelleme yapıyorum.


21
Aslında bu en sağlam ve ölçeklenebilir kurumsal çözümdür.
Lavir the Whiolet

12
@Lavir, kötü bir çözüm değil, ama en sağlam ve ölçeklenebilir nasıl olduğunu görmüyorum. Bunun yerine bir atomik birleştirici çok daha ölçeklenebilir.
John Vint

13
bu anahtarın var olduğunu varsayar, değil mi? Olmadığında nullPointer Exception alıyorum.
Ian

84
Java 8 ile bu, getOrDefaultörneğin aşağıdakiler kullanılarak kolayca önlenebilir :map.put(key, count.getOrDefault(key, 0) + 1);
Martin

2
@Martin .. map.put (anahtar, map.getOrVarsayılan (anahtar, 0) + 1)
Sathesh

112

Java 8 yolu:

computeIfPresentYöntemi kullanabilir ve ona, mevcut olana dayalı yeni bir değer hesaplamak için çağrılacak bir eşleme işlevi sağlayabilirsiniz.

Örneğin,

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

Alternatif olarak, merge1'i varsayılan değer ve fonksiyonun mevcut değeri 1 arttırdığı yöntemi kullanabilirsiniz :

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

Buna ek olarak, bu tür başka yararlı yöntem, bir grup olduğu putIfAbsent, getOrDefault, forEachvs.


3
Az önce çözümlerinizi test ettim. İkincisi, yöntem referansı olan, çalışır. İlki, lambda ifadesi, haritanızın herhangi bir değeri null(diyelim words.put("hello", null);) olduğunda tutarlı bir şekilde çalışmıyor , sonuç hala beklediğim gibi nulldeğil 1.
Tao Zhang

4
Javadoc'dan: "Belirtilen anahtarın değeri varsa ve null değilse, yeni bir eşleme hesaplamaya çalışır". Bunun compute()yerine, nulldeğerleri de işleyebilir .
damluar

Değerimi 1 arttırmak istiyorum . .mergeBenim çözümüm Integer::sum.
S_K

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

Yöntem put, var olan bir anahtarın değerini değiştirecek ve yoksa anahtar oluşturacaktır.


55
Hayır, yaratmaz, verir nullPointer Exception.
smttsp

13
Kod, verilen soru için doğru bir cevaptır, ancak aynı kod kabul edilen cevapta yayınlandıktan bir yıl sonra yayınlanmıştır. Bu cevabı farklı kılan şey koymak olduğunu belirtmek, ancak bu örnekte değil, yeni bir giriş oluşturabilir. Varolmayan bir anahtar / değer için hashmap.get (anahtar) kullanıyorsanız null alırsınız ve artırmaya çalıştığınızda @smttsp NPE olacağını söyler. -1
Zach

8
Bu cevap yanlış. Mevcut olmayan anahtarlar için NullPointerException
Eleanore

@smttp NullpointterException yalnızca değeri başlatmadıysanız (bildiğiniz gibi null değerini artıramazsınız)
Mehdi

Çoğaltma ve yanlış açıklama ... yoksa yoksa oluşturur. Bunu yapamazsınız null + 1çünkü bu null, artışı yapmak için bir tamsayıya ayırmayı deneyecektir .
AxelH


30

Değiştir Integertarafından AtomicIntegerve birini aramak incrementAndGet/ getAndIncrementÜzerinde yöntemlerle.

Bir alternatif, intkendi MutableIntegersınıfınızda bir increment()yöntemi olan bir sarmaktır, henüz çözmeniz gereken bir iplik güvenliği endişeniz vardır.


37
AtomicInteger Değişken Bir Tamsayıdır ancak yerleşiktir. Kendi MutableInteger'ınızı yazmaktan ciddi olarak şüpheliyim daha iyi bir fikir.
Peter Lawrey

Custom MutableInteger, yükleri olan AtomicIntegerkullanımlar olarak daha iyidir volatile. Bunun int[1]yerine kullanırdım MutableInteger.
Oliv

@Oliv: eşzamanlı değil.
BalusC

@BalusC ama yine de, uçucu yazma daha pahalıdır. Önbellekleri geçersiz kılar. Eğer fark olmasaydı, tüm değişkenler değişken olurdu.
Oliv

@Oliv: soru açıkça hashcode çarpışmasından bahsediyor, bu nedenle eşzamanlılık OP için önemlidir.
BalusC

19

Tek hat çözümü:

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

4
Bu mevcut cevaplara yeni bir şey katmıyor, değil mi?
Robert

1
Evet öyle. Doğru işaretli yanıt, anahtar yoksa bir NullPointerException özel durumu oluşturur. Bu çözüm işe yarayacak.
Hemant Nagpal

18

@ Matthew'un çözümü en basit olanıdır ve çoğu durumda yeterince iyi performans gösterir.

Yüksek performansa ihtiyacınız varsa, AtomicInteger ala @BalusC için daha iyi bir çözümdür.

Bununla birlikte, bir artış (anahtar) yöntemi sağlayan ve AtomicIntegers oluşturmaktan daha temel ve daha az nesne kullanan TObjectIntHashMap kullanmak daha hızlı bir çözümdür (iş parçacığı güvenliği bir sorun değildir) . Örneğin

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>()
map.increment("aaa");

13

Aşağıdaki gibi arttırabilirsiniz, ancak bir NullPointerException oluşturulmaması için varlığını kontrol etmeniz gerekir

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

9

Karma var mı (değer olarak 0 ile) mi yoksa ilk artışta haritaya "konuldu" mu? İlk artışa "koyulursa" kod şöyle görünmelidir:

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

7

Biraz geç olabilir ama işte benim iki sentim.

Java 8 kullanıyorsanız, computeIfPresent yöntemini kullanabilirsiniz . Belirtilen anahtarın değeri varsa ve null değilse, anahtar ve geçerli eşlenen değeri göz önüne alındığında yeni bir eşleme hesaplamaya çalışır.

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]

Bir anahtar koymak için putIfAbsent yöntemini de kullanabiliriz . Belirtilen anahtar zaten bir değerle ilişkilendirilmemişse (veya null ile eşlenmişse), bu yöntem bunu verilen değerle ilişkilendirir ve null değerini döndürür, aksi takdirde geçerli değeri döndürür.

Haritanın iş parçacıkları arasında paylaşılması durumunda ConcurrentHashMapve AtomicInteger'den yararlanabiliriz . Dokümandan:

A AtomicInteger, atomik olarak güncellenebilecek bir int değeridir. AtomicInteger, atomik olarak artırılmış sayaçlar gibi uygulamalarda kullanılır ve bir Tamsayı'nın yerine kullanılamaz. Ancak, bu sınıf, sayısal tabanlı sınıflarla ilgilenen araç ve yardımcı programlarla eşit erişim sağlamak için Number'ı genişletir.

Bunları gösterildiği gibi kullanabiliriz:

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]

Gözlemlememiz gereken bir nokta, getanahtarın değerini almaya Bçağırıyoruz ve sonra incrementAndGet()elbette ki değerini çağırıyoruz AtomicInteger. Yöntem putIfAbsent, zaten varsa anahtarın değerini döndürdüğü için optimize edebiliriz :

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

Kullandığımız planlıyorsanız Yan not AtomicLong verimi beklenen yüksek çekişme altında belgelendirmesi olarak daha sonra LongAdder yüksek uzay tüketimi pahasına, önemli ölçüde daha yüksektir. Ayrıca bu soruyu kontrol edin .


5

NullPointerException içermeyen daha temiz çözüm:

map.replace(key, map.get(key) + 1);

5
anahtar yoksa, map.get (anahtar) 21: 33'te NPE
Navi

Evet bu doğru
Sergey Dirin

2

Daha az itibar nedeniyle birkaç cevaba yorum yapamadığım için, uyguladığım bir çözümü yayınlayacağım.

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

forDizini artırmak için bir döngü kullanın :

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
Bu, her yinelemede haritanın üzerine yazacaktır. Doğru yaklaşım için Matthew'in cevabına bakınız.
Leigh


1
Integer i = map.get(key);
if(i == null)
   i = (aValue)
map.put(key, i + 1);

veya

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

Tamsayı ilkel veri türleridir http://cs.fit.edu/~ryan/java/language/java-data.html , bu yüzden çıkarmanız, biraz işlem yapmanız ve sonra geri koymanız gerekir. İlkel veri türleri olmayan bir değeriniz varsa, sadece onu çıkarmanız, işlemeniz, hashete geri koymanıza gerek yoktur.


1
Anında yardım sağlayabilecek bu kod snippet'i için teşekkür ederiz. Düzgün bir açıklama , bunun neden problem için iyi bir çözüm olduğunu göstererek eğitim değerini büyük ölçüde artıracak ve benzer, ancak aynı olmayan soruları olan gelecekteki okuyucular için daha yararlı hale getirecektir. Lütfen açıklama eklemek için cevabınızı düzenleyin ve hangi sınırlamaların ve varsayımların geçerli olduğunu belirtin.
Toby Speight

0

Deneyin:

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

NOT:

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

Anahtarınız veya hashutunuzdaki değeri değiştirebilirsiniz, ancak ikisini aynı anda değiştiremezsiniz.


0

Yerleşik Java8 işlevini 'computeIfPresent' kullanın

Misal:

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);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.