Java'da bir Harita değerini artırmanın en etkili yolu


377

Umarım bu soru bu forum için çok temel kabul edilmez, ancak göreceğiz. Bir sürü kez çalıştırmak daha iyi performans için bazı kodu refactor merak ediyorum.

Diyelim ki, her anahtarın sayılan kelimeyi içeren bir Dize olduğu ve her kelimenin belirteci bulunduğunda değeri artan bir Tamsayı olan bir Harita (muhtemelen bir HashMap) kullanarak bir sözcük sıklığı listesi oluşturuyorum.

Perl'de böyle bir değeri arttırmak çok kolay olurdu:

$map{$word}++;

Ancak Java'da çok daha karmaşıktır. İşte şu anda bunu yapıyorum:

int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);

Elbette yeni Java sürümlerindeki otomatik boks özelliğine dayanmaktadır. Böyle bir değeri artırmanın daha verimli bir yolunu önerebilir misiniz acaba? Koleksiyonlar çerçevesinden kaçınmak ve bunun yerine başka bir şey kullanmak için iyi performans nedenleri var mı?

Güncelleme: Birkaç cevabı test ettim. Aşağıya bakınız.


Java.util.Hashtable için de aynı olacağını düşünüyorum.
jrudolph

2
Elbette aynı olurdu, çünkü Hashtable bir Harita infact olduğunu.
whiskeysierra

Java 8: computeIfAbsent örneği: stackoverflow.com/a/37439971/1216775
akhil_mittal

Yanıtlar:


366

Bazı test sonuçları

Bu soruya çok iyi cevaplar aldım - teşekkürler millet - bu yüzden bazı testler yapmaya ve hangi yöntemin gerçekten en hızlı olduğunu anlamaya karar verdim. Test ettiğim beş yöntem şunlardır:

  • soruda sunduğum "ContainsKey" yöntemi
  • Aleksandar Dimitrov tarafından önerilen "TestForNull" yöntemi
  • Hank Gay tarafından önerilen "AtomicLong" yöntemi
  • jrudolph tarafından önerilen "Trove" yöntemi
  • phax.myopenid.com tarafından önerilen "MutableInt" yöntemi

Yöntem

İşte yaptığım şey ...

  1. aşağıda gösterilen farklar dışında özdeş olan beş sınıf yarattı. Her sınıf, sunduğum senaryo için tipik bir işlem yapmak zorundaydı: 10MB'lık bir dosyayı açmak ve okumak, daha sonra dosyadaki tüm kelime belirteçlerinin bir frekans sayımı yapmak. Bu sadece ortalama 3 saniye sürdüğünden, frekans sayımını (G / Ç değil) 10 kez yaptım.
  2. I / O operasyonunu değil 10 tekrarlama döngüsünü zamanladı ve esasen Ian Darwin'in Java Yemek Kitabı'ndaki yöntemini kullanarak toplam süreyi (saat cinsinden) kaydetti .
  3. beş testi de seri olarak gerçekleştirdi ve bunu üç kez daha yaptı.
  4. her yöntem için dört sonucun ortalaması alınmıştır.

Sonuçlar

İlgilenenler için önce sonuçları ve aşağıdaki kodu sunacağım.

ContainsKey O yöntemin hızına kıyasla her bir yöntemin hızını vereceğiz böylece yöntem, en yavaş beklendiği gibi.

  • Anahtar : 30.654 saniye (taban çizgisi)
  • AtomicLong: 29.780 saniye (1.03 kat daha hızlı)
  • TestForNull: 28.804 saniye (1,06 kat daha hızlı)
  • Trove: 26.313 saniye (1,16 kat daha hızlı)
  • Değişken: 25.747 saniye (1.19 kat daha hızlı)

Sonuçlar

Sadece MutableInt yönteminin ve Trove yönteminin önemli ölçüde daha hızlı olduğu görülüyor, çünkü sadece% 10'dan fazla bir performans artışı sağlıyorlar. Bununla birlikte, iplik geçirme bir sorunsa, AtomicLong diğerlerinden daha çekici olabilir (gerçekten emin değilim). TestForNull'ı finaldeğişkenlerle de çalıştırdım , ancak fark önemsizdi.

Farklı senaryolarda bellek kullanımını profilli olmadığımı unutmayın. MutableInt ve Trove yöntemlerinin bellek kullanımını nasıl etkileyebileceğine dair iyi fikir sahibi olan herkesi duymaktan mutluluk duyarım.

Şahsen, MutableInt yöntemini en çekici buluyorum, çünkü herhangi bir üçüncü taraf sınıfı yüklemeyi gerektirmiyor. Dolayısıyla, onunla ilgili sorunlar keşfetmezsem, gitme ihtimalim budur.

Kod

İşte her yöntemin önemli kodu.

ContainsKey

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);

TestForNull

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
    freq.put(word, 1);
}
else {
    freq.put(word, count + 1);
}

AtomicLong

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map = 
    new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();

define

import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);

MutableInt

import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
  int value = 1; // note that we start at 1 since we're counting
  public void increment () { ++value;      }
  public int  get ()       { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
    freq.put(word, new MutableInt());
}
else {
    count.increment();
}

3
Harika iş, aferin. Küçük bir yorum - AtomicLong kodundaki putIfAbsent () çağrısı, haritada zaten olsa bile yeni bir AtomicLong (0) başlatır. Bunun yerine if (map.get (key) == null) işlevini kullanmak için ince ayar yaparsanız, muhtemelen bu test sonuçlarında bir iyileşme elde edersiniz.
Leigh Caldwell

2
Aynı şeyi son zamanlarda MutableInt'e benzer bir yaklaşımla yaptım. En iyi çözüm olduğunu duyduğuma sevindim (herhangi bir test yapmadan öyle olduğunu varsaydım).
Kip

Benden daha hızlı olduğunu duymak güzel, Kip. ;-) Bu yaklaşımın herhangi bir dezavantajı fark ederseniz bana bildirin.
gregory

4
Atomic Long durumunda bunu tek adımda yapmak daha verimli olmaz (2 yerine sadece 1 pahalı alma işleminiz olur) "map.putIfAbsent (word, new AtomicLong (0)). İncrementAndGet ();"
smartnut007

1
@gregory Java 8'i düşündünüz mü freq.compute(word, (key, count) -> count == null ? 1 : count + 1)? Dahili olarak daha az karma bir arama yapar containsKey, lambda nedeniyle diğerleriyle nasıl karşılaştırıldığını görmek ilginç olurdu.
TWiStErRob

255

Şimdi Java 8 ile daha kısa bir yol var Map::merge.

myMap.merge(key, 1, Integer::sum)

Bu ne yapar:

  • eğer anahtar var yok, koyun 1 değeri olarak
  • aksi takdirde anahtarla bağlantılı değere 1 toplamı

Daha fazla bilgi burada .


her zaman java 8'i seviyorum. Bu atomik mi? ya da senkronize edilmiş mi?
Tiina

4
bu benim için işe yaramadı ama işe map.merge(key, 1, (a, b) -> a + b); yaramadı
russter

2
@Tiina Atomisite özellikleri uygulamaya özgüdür, bkz. docs : "Varsayılan uygulama, bu yöntemin senkronizasyon veya atomisite özellikleri hakkında hiçbir garanti vermez. Atomisite garantileri sağlayan herhangi bir uygulama, bu yöntemi geçersiz kılmalı ve eşzamanlılık özelliklerini belgelemelidir. Özellikle, ConcurrentMap alt arabiriminin tüm uygulamaları, işlevin bir kez uygulanıp uygulanmadığını belgelemelidir atomik olarak sadece değer yoksa. "
jensgram

2
Harika için, Integer::sumbir BiFunction olarak kabul etmeyecek ve @russter'ın yazılma şekline cevap vermesini beğenmeyecekti. Bu benim için çalıştıMap.merge(key, 1, { a, b -> a + b})
jookyone

2
@russter, yorumunuzun bir yıl önce bittiğini biliyorum, ancak bunun sizin için neden işe yaramadığını hatırlıyor musunuz? Bir derleme hatası mı aldınız veya değer artırılmadı mı?
Paul

44

2016'da küçük bir araştırma: https://github.com/leventov/java-word-count , referans kaynak kodu

Yöntem başına en iyi sonuçlar (daha küçük daha iyidir):

                 time, ms
kolobokeCompile  18.8
koloboke         19.8
trove            20.8
fastutil         22.7
mutableInt       24.3
atomicInteger    25.3
eclipse          26.9
hashMap          28.0
hppc             33.6
hppcRt           36.5

Zaman \ boşluk sonuçları:


2
Teşekkürler, bu gerçekten yardımcı oldu. Guava'nın Multiset'ini (örneğin, HashMultiset) karşılaştırmaya eklemek güzel olurdu.
cabad

34

Google Guava senin arkadaşın ...

... en azından bazı durumlarda. Bu güzel AtomicLongMap'e sahipler . Özellikle güzel çünkü haritanızda değer kadar uzun süre uğraşıyorsunuz .

Örneğin

AtomicLongMap<String> map = AtomicLongMap.create();
[...]
map.getAndIncrement(word);

Değere 1'den fazla eklemek de mümkündür:

map.getAndAdd(word, 112L); 

7
AtomicLongMap#getAndAddlongsarmalayıcı sınıfı değil ilkel bir sınıf alır; yapmanın bir anlamı yok new Long(). Ve AtomicLongMapparametreli bir tiptir; olarak ilan etmiş olmalısınız AtomicLongMap<String>.
Helder Pereira

32

Eşcinsel

Kendi (oldukça yararsız) yorumumun bir devamı olarak: Trove gidilecek yol gibi görünüyor. Sebebi ne olursa olsun, standart JDK ile devam etmek istediğini, Eğer ConcurrentMap ve AtomicLong kodu yapabilirsiniz küçücük biraz daha güzel, YMMV olsa.

    final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.putIfAbsent("foo", new AtomicLong(0));
    map.get("foo").incrementAndGet();

bırakacaktır 1için haritadaki değeri olarak foo. Gerçekçi olarak, bu yaklaşımın önerdiği tek şey diş çekme ile artan dostluktur.


9
PutIfAbsent () değeri döndürür. Döndürülen değeri yerel bir değişkende saklamak ve tekrar almak yerine artrAndGet () yönteminde kullanmak büyük bir iyileştirme olabilir.
smartnut007

Belirtilen anahtar zaten Harita içindeki bir değerle ilişkilendirilmemişse putIfAbsent null değer döndürebilir, bu nedenle döndürülen değeri kullanmaya dikkat ederim. docs.oracle.com/javase/8/docs/api/java/util/…
bumbur

27
Map<String, Integer> map = new HashMap<>();
String key = "a random key";
int count = map.getOrDefault(key, 0); // ensure count will be one of 0,1,2,3,...
map.put(key, count + 1);

Ve bu şekilde bir değeri basit kodla artırırsınız.

Yarar:

  • Yeni bir sınıf eklemenize veya başka bir mutable int kavramı kullanmaya gerek yok
  • Hiçbir kütüphaneye güvenmemek
  • Tam olarak neler olduğunu anlamak kolay (Çok fazla soyutlama değil)

Dezavantaj:

  • Karma haritası get () ve put () için iki kez aranacaktır. Bu yüzden en performanslı kod olmayacak.

Teorik olarak, get () öğesini çağırdığınızda, nereye () koyacağınızı zaten biliyorsunuzdur, bu yüzden tekrar arama yapmak zorunda kalmamalısınız. Ancak karma haritada arama yapmak genellikle bu performans sorununu göz ardı edebileceğiniz çok az zaman alır.

Ancak bu konuda çok ciddiyseniz, mükemmeliyetçisiniz, birleştirme yöntemini kullanmanın başka bir yolu da, (muhtemelen) önceki kod snippet'inden (muhtemelen) haritayı yalnızca bir kez arayacağınızdan daha etkilidir: bu kod ilk bakışta belirgin değil, kısa ve performanslıdır)

map.merge(key, 1, (a,b) -> a+b);

Öneri: Kod okunabilirliğini çoğu zaman düşük performans kazancından daha fazla önemsemelisiniz. İlk kod snippet'ini anlamanız daha kolaysa bunu kullanın. Ama 2. para cezasını anlayabiliyorsanız, o zaman da gidebilirsiniz!


GetOfDefault yöntemi JAVA 7'de mevcut değildir. Bunu JAVA 7'de nasıl başarabilirim?
tanvi

1
O zaman başka cevaplara güvenmeniz gerekebilir. Bu yalnızca Java 8'de çalışır.
off99555

1
Birleştirme çözümü için +1, bu en yüksek performanslı işlev olacaktır, çünkü potansiyel olarak ödeme yapmak yerine, hashcode hesaplaması için yalnızca 1 kez ödeme yapmanız gerekir (kullandığınız Harita'nın yöntemi düzgün bir şekilde desteklemesi durumunda) 3 kez
Ferrybig

2
Yöntem çıkarımını kullanarak: map.merge (anahtar, 1, Tamsayı :: toplam)
earandap

25

Bu tür şeyler için Google Koleksiyonlar Kütüphanesine bakmak her zaman iyi bir fikirdir . Bu durumda bir Multiset hile yapar:

Multiset bag = Multisets.newHashMultiset();
String word = "foo";
bag.add(word);
bag.add(word);
System.out.println(bag.count(word)); // Prints 2

Anahtarlar / girişler, vb. Üzerinde yineleme yapmak için Harita benzeri yöntemler vardır HashMap<E, AtomicInteger>.


Yukarıdaki cevaplayıcı, tovares tepkisini yansıtmalıdır. Api, ilanından bu yana değişti (3 yıl önce :))
Steve

count()O (1) ya da O (n), zaman (en kötü durum) 'de de multiset kaçak yöntemi? Dokümanlar bu noktada net değil.
Adam Parkin

Bu tür şeyler için algoritmam: if (hasApacheLib (thing)) return apacheLib; else (hasOnGuava (şey)) guava döndürürse. Genellikle bu iki adımı atlamıyorum. :)
digao_mb

22

Orijinal girişiminizin

int count = map.containsKey (sözcük)? map.get (kelime): 0;

Bir harita, yani iki potansiyel pahalı operasyonlar içerir containsKeyve get. Birincisi, ikincisine oldukça benzer bir işlem gerçekleştirir, böylece aynı işi iki kez yaparsınız !

Harita API'sına bakarsanız, getişlemler genelliklenull harita istenen öğeyi içermiyorsa .

Bunun gibi bir çözüm yapacağını unutmayın

map.put (anahtar, map.get (anahtar) + 1);

tehlikeli olabilir, çünkü NullPointerExceptions verebilir . İlkini kontrol etmelisin null.

Ayrıca dikkat ve bu o, çok önemli HashMapler yapabilirsiniz ihtiva nullstanımı gereği. Yani her geri dönen null"böyle bir unsur yok" demiyor. Bu bağlamda, containsKeydavranır farklı dan getaslında söylüyorum içinde olsun böyle bir unsur yoktur. Ayrıntılar için API'ya bakın.

Ancak, sizin durumunuz için, saklanan nullve "noSuchElement" arasında ayrım yapmak istemeyebilirsiniz. Eğer izin vermek istemiyorsanız nulla Hashtable. Diğer yanıtlarda önerildiği gibi bir sarıcı kitaplığı kullanmak, uygulamanızın karmaşıklığına bağlı olarak manuel tedaviye daha iyi bir çözüm olabilir.

Cevabı tamamlamak için (ve ilk başta düzenleme işlevi sayesinde bunu koymayı unuttum!), Doğal olarak yapmanın en iyi yolu getbir finaldeğişkene girmektir, kontrol edin nullve puta ile geri getirin 1. Değişken, finalzaten değişmez olduğu için olmalıdır . Derleyicinin bu ipucuna ihtiyacı olmayabilir, ama bu şekilde daha nettir.

son HashMap haritası = createRandomHashMap ();
son Nesne anahtarı = fetchSomeKey ();
son Tamsayı i = map.get (anahtar);
if (i! = null) {
    map.put (i + 1);
} Başka {
    // bir şey yap
}

Otomatik kutulamaya güvenmek istemiyorsanız, map.put(new Integer(1 + i.getValue()));bunun gibi bir şey söylemelisiniz .


Harika ilk ilk eşlenmemiş / null değerleri sorununu önlemek için sonunda: counts.put (anahtar, (counts.get (anahtar)?: 0) + 1) // ++ aşırı karmaşık sürümü
Joe Atzberger

2
Ya da, en basit haliyle: sayar = [:] .Default {0} // ++ away
Joe Atzberger

18

Başka bir yol, değiştirilebilir bir tamsayı oluşturmaktır:

class MutableInt {
  int value = 0;
  public void inc () { ++value; }
  public int get () { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt> ();
MutableInt value = map.get (key);
if (value == null) {
  value = new MutableInt ();
  map.put (key, value);
} else {
  value.inc ();
}

Tabii ki bu ek bir nesne oluşturmayı gerektirir, ancak bir Integer (Integer.valueOf ile bile) oluşturmaya kıyasla ek yük çok fazla olmamalıdır.


5
MutableInt'i haritaya ilk koyduğunuzda 1'de başlatmak istemez misiniz?
Tom Hawtin - tackline

5
Apache'nin müşterek dilinin sizin için önceden yazılmış bir MutableInt'i var.
SingleShot

11

Java 8'de sağlanan arabirimde computeIfAbsent yöntemini kullanabilirsiniz .Map

final Map<String,AtomicLong> map = new ConcurrentHashMap<>();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("B", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet(); //[A=2, B=1]

Yöntem computeIfAbsent , belirtilen anahtarın zaten bir değerle ilişkili olup olmadığını kontrol eder? İlişkili bir değer yoksa, verilen eşleme işlevini kullanarak değerini hesaplamaya çalışır. Her durumda, belirtilen anahtarla ilişkilendirilmiş geçerli (var olan veya hesaplanan) değeri veya hesaplanan değer null ise null değerini döndürür.

Yan notta, birden fazla iş parçacığının ortak bir toplamı güncellediği bir durumunuz varsa, LongAdder sınıfına çekişme altında, bu sınıfın beklenen verimi, daha AtomicLongyüksek alan tüketimi pahasına önemli ölçüde daha yüksektir .


neden eşzamanlıHashmap ve AtomicLong?
ealeon

7

Bellek döndürme burada bir sorun olabilir, çünkü 128'den büyük veya buna eşit bir int'in her boksu bir nesne tahsisine neden olur (bkz. Integer.valueOf (int)). Çöp toplayıcı kısa ömürlü nesnelerle çok verimli bir şekilde ilgilenmesine rağmen, performans bir dereceye kadar acı çekecektir.

Yapılan artış sayısının anahtar sayısından (bu durumda = kelime) daha fazla sayılacağını biliyorsanız, bunun yerine bir int tutucu kullanmayı düşünün. Phax bunun için zaten kod sundu. İşte yine, iki değişiklikle (tutucu sınıfı statik hale getirildi ve başlangıç ​​değeri 1 olarak ayarlandı):

static class MutableInt {
  int value = 1;
  void inc() { ++value; }
  int get() { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt>();
MutableInt value = map.get(key);
if (value == null) {
  value = new MutableInt();
  map.put(key, value);
} else {
  value.inc();
}

Aşırı performansa ihtiyacınız varsa, doğrudan ilkel değer türlerine göre uyarlanmış bir Harita uygulaması arayın. jrudolph GNU Trove'den bahsetti .

Bu arada, bu konu için iyi bir arama terimi "histogram" dır.


5

İncludeKey () öğesini çağırmak yerine sadece map.get öğesini çağırmak ve döndürülen değerin boş olup olmadığını kontrol etmek daha hızlıdır.

    Integer count = map.get(word);
    if(count == null){
        count = 0;
    }
    map.put(word, count + 1);

3

Bunun bir darboğaz olduğundan emin misiniz? Performans analizi yaptınız mı?

Sıcak noktalara bakmak için NetBeans profilini (ücretsiz ve NB 6.1 içine yerleştirilmiş) kullanmayı deneyin.

Son olarak, bir JVM yükseltmesi (1.5-> 1.6'dan) genellikle ucuz bir performans yükseltici. Yapı numarasındaki bir yükseltme bile iyi performans artışı sağlayabilir. Windows üzerinde çalışıyorsanız ve bu bir sunucu sınıfı uygulamasıysa, Sunucu Hotspot JVM'sini kullanmak için komut satırında -server kullanın. Linux ve Solaris makinelerinde bu otomatik olarak algılanır.


3

Birkaç yaklaşım var:

  1. Google Koleksiyonlarında bulunan setler gibi bir Çanta aloriti kullanın.

  2. Haritada kullanabileceğiniz değiştirilebilir bir kap oluşturun:


    class My{
        String word;
        int count;
    }

Ve put ("word", new My ("Word")); Ardından, var olup olmadığını kontrol edin ve eklerken artırın.

Listeleri kullanarak kendi çözümünüzü yuvarlamaktan kaçının, çünkü iç döngü arama ve sıralama yaparsanız, performansınız kıyacaktır. İlk HashMap çözümü aslında oldukça hızlı, ancak Google Koleksiyonlarında bulunanlar gibi uygun bir olasılık muhtemelen daha iyi.

Google Koleksiyonlarını kullanarak kelime saymak, şuna benzer:



    HashMultiset s = new HashMultiset();
    s.add("word");
    s.add("word");
    System.out.println(""+s.count("word") );


HashMultiset'i kullanmak oldukça şıktır, çünkü bir torba algoritması kelimeleri sayarken ihtiyacınız olan şeydir.


3

Çözümünüzün standart yol olacağını düşünüyorum, ama - kendinizi belirttiğiniz gibi - muhtemelen mümkün olan en hızlı yol değildir.

GNU Trove'a bakabilirsiniz . Bu, her türlü hızlı ilkel Koleksiyon içeren bir kütüphane. Örneğin , tam olarak ne istediğinizi yapan bir adjustOrPutValue yöntemine sahip bir TObjectIntHashMap kullanırsınız .


TObjectIntHashMap bağlantısı kesildi. Bu doğru bağlantı: trove4j.sourceforge.net/javadocs/gnu/trove/map/…
Erel Segal-Halevi

Teşekkürler, Erel, bağlantıyı düzelttim.
jrudolph

3

MutableInt yaklaşımında, bir saldırı birazcık, daha hızlı olabilecek bir varyasyon, tek öğeli bir int dizisi kullanmaktır:

Map<String,int[]> map = new HashMap<String,int[]>();
...
int[] value = map.get(key);
if (value == null) 
  map.put(key, new int[]{1} );
else
  ++value[0];

Performans testlerinizi bu varyasyonla tekrar çalıştırabilmeniz ilginç olurdu. En hızlı olabilir.


Düzenleme: Yukarıdaki desen benim için iyi çalıştı, ama sonunda Trove's koleksiyonları oluşturmakta olduğum bazı çok büyük haritalar bellek boyutunu azaltmak için değişti - ve bir bonus olarak da daha hızlı oldu.

Gerçekten güzel bir özellik, TObjectIntHashMapsınıfın, adjustOrPutValueo anahtarda zaten bir değer olup olmadığına bağlı olarak, ya bir başlangıç ​​değeri koyacak ya da mevcut değeri artıracak tek bir çağrı olmasıdır. Bu artış için mükemmeldir:

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>();
...
map.adjustOrPutValue(key, 1, 1);

3

Google Koleksiyonlar HashMultiset:
- kullanımı oldukça zarif
- ancak CPU ve bellek tüketiyor

En iyisi şöyle bir yönteme sahip olmak olacaktır: Entry<K,V> getOrPut(K); (zarif ve düşük maliyetli)

Böyle bir yöntem, hash ve index'i yalnızca bir kez hesaplar ve daha sonra girişle istediğimizi yapabiliriz (değeri değiştirin veya güncelleyin).

Daha zarif:
- a HashSet<Entry>
- uzatın, get(K)gerekirse yeni bir Giriş yerleştirin
- Giriş kendi nesneniz olabilir.
->(new MyHashSet()).get(k).increment();


3

Oldukça basit, sadece yerleşik işlevi aşağıdaki Map.javagibi kullanın

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

Bu, değeri artırmaz, yalnızca geçerli değeri veya tuşa herhangi bir değer atanmamışsa 0 değerini ayarlar.
siegi

Değeri artırabilirsiniz ++... OMG, çok basit. @siegi
sudoz

Kayıt için: ++işleci olarak bir değişkene ihtiyaç duyulduğu için bu ifadede hiçbir yerde çalışmaz, ancak sadece değerler vardır. + 1Gerçi işlerin eklenmesi . Şimdi çözümünüz off99555s cevabı ile aynıdır .
siegi

2

"put" need "get" (yinelenen anahtar olmadığından emin olmak için).
Bu yüzden doğrudan bir "koy" yapın
ve önceki bir değer varsa, bir ekleme yapın:

Map map = new HashMap ();

MutableInt newValue = new MutableInt (1); // default = inc
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
  newValue.add(oldValue); // old + inc
}

Sayım 0'dan başlarsa, 1: (veya başka herhangi bir değer ekleyin ...)

Map map = new HashMap ();

MutableInt newValue = new MutableInt (0); // default
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
  newValue.setValue(oldValue + 1); // old + inc
}

Uyarı: Bu kod iş parçacığı için güvenli değildir. Eşzamanlı olarak güncellemek için değil, haritayı oluşturmak için kullanın.

Optimizasyon: Bir döngüde, bir sonraki döngünün yeni değeri olmak için eski değeri koruyun.

Map map = new HashMap ();
final int defaut = 0;
final int inc = 1;

MutableInt oldValue = new MutableInt (default);
while(true) {
  MutableInt newValue = oldValue;

  oldValue = map.put (key, newValue); // insert or...
  if (oldValue != null) {
    newValue.setValue(oldValue + inc); // ...update

    oldValue.setValue(default); // reuse
  } else
    oldValue = new MutableInt (default); // renew
  }
}


1

Apache Collections Lazy Map (değerleri 0 olarak başlatmak için) ve bu haritada değerler olarak Apache Lang MutableIntegers kullanın.

En büyük maliyet, haritanızı yönteminize iki kez yerleştirmek zorunda kalır. Benimkinde sadece bir kez yapmalısın. Sadece değeri alın (yoksa sıfırlanır) ve artırın.


1

Fonksiyonel Java kütüphanenin TreeMapveri tipleri de bir sahip updateson gövde kafasında yöntemi:

public TreeMap<K, V> update(final K k, final F<V, V> f)

Örnek kullanım:

import static fj.data.TreeMap.empty;
import static fj.function.Integers.add;
import static fj.pre.Ord.stringOrd;
import fj.data.TreeMap;

public class TreeMap_Update
  {public static void main(String[] a)
    {TreeMap<String, Integer> map = empty(stringOrd);
     map = map.set("foo", 1);
     map = map.update("foo", add.f(1));
     System.out.println(map.get("foo").some());}}

Bu program "2" yazdırır.


1

@Vilmantas Baranauskas: Bu cevap ile ilgili olarak, ben tekrarlama noktalarına sahip olsaydım yorum yapardım, ama ben yokum. Sayaç sınıf tanımlanmış değil sadece değer () senkronize etmeden inc () senkronize etmek için yeterli değil iş parçacığı güvenli olduğunu not etmek istedim. Value () öğesini çağıran diğer evreler, güncellemeyle önce bir ilişki kurulmadıkça bu değeri göreceği garanti edilmez.


Birinin cevabına referans vermek istiyorsanız, üstteki @ [Kullanıcı adı] 'nı kullanın, örn., @Vilmantas Baranauskas <İçerik buraya gelir>
Hank Gay

Temizlemek için bu değişikliği yaptım.
Alex Miller

1

Ne kadar verimli olduğunu bilmiyorum ama aşağıdaki kod da çalışıyor BiFunction. Başlangıçta bir tanımlamanız gerekir . Ayrıca, bu yöntemle artırmaktan daha fazlasını yapabilirsiniz.

public static Map<String, Integer> strInt = new HashMap<String, Integer>();

public static void main(String[] args) {
    BiFunction<Integer, Integer, Integer> bi = (x,y) -> {
        if(x == null)
            return y;
        return x+y;
    };
    strInt.put("abc", 0);


    strInt.merge("abc", 1, bi);
    strInt.merge("abc", 1, bi);
    strInt.merge("abc", 1, bi);
    strInt.merge("abcd", 1, bi);

    System.out.println(strInt.get("abc"));
    System.out.println(strInt.get("abcd"));
}

çıktı

3
1

1

Eclipse Collections kullanıyorsanız , bir HashBag. Bellek kullanımı açısından en verimli yaklaşım olacak ve yürütme hızı açısından da iyi performans gösterecek.

HashBagnesneleri MutableObjectIntMapyerine ilkel ints depolayan bir tarafından desteklenir Counter. Bu, bellek yükünü azaltır ve yürütme hızını artırır.

HashBag ihtiyaç duyduğunuz API'yı, Collectionbir öğenin tekrarlama sayısını sorgulamanıza olanak tanıdığı için sağlar.

İşte bir örnek Eclipse Collections Kata'dan .

MutableBag<String> bag =
  HashBag.newBagWith("one", "two", "two", "three", "three", "three");

Assert.assertEquals(3, bag.occurrencesOf("three"));

bag.add("one");
Assert.assertEquals(2, bag.occurrencesOf("one"));

bag.addOccurrences("one", 4);
Assert.assertEquals(6, bag.occurrencesOf("one"));

Not: Eclipse Collections için bir komisyoncuyum.


1

Java 8 Map :: compute () kullanmanızı öneririm. Bir anahtarın bulunmadığı durumu da dikkate alır.

Map.compute(num, (k, v) -> (v == null) ? 1 : v + 1);

mymap.merge(key, 1, Integer::sum)?
Det

-2

Birçok kişi Groovy yanıtları için Java konularını aradığından, Groovy'de nasıl yapabileceğiniz aşağıda açıklanmıştır:

dev map = new HashMap<String, Integer>()
map.put("key1", 3)

map.merge("key1", 1) {a, b -> a + b}
map.merge("key2", 1) {a, b -> a + b}

-2

Java 8'deki basit ve kolay yol şudur:

final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.computeIfAbsent("foo", key -> new AtomicLong(0)).incrementAndGet();

-3

Umarım sorunuzu doğru anlıyorum, mücadelenizle empati kurabilmem için Python'dan Java'ya geliyorum.

eğer varsa

map.put(key, 1)

yapardın

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

Bu yardımcı olur umarım!

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.