Il modo più efficiente per incrementare un valore della mappa in Java


377

Spero che questa domanda non sia considerata troppo semplice per questo forum, ma vedremo. Mi chiedo come rielaborare un po 'di codice per prestazioni migliori che vengono eseguite un sacco di volte.

Supponiamo che io stia creando un elenco di frequenze di parole, usando una mappa (probabilmente una HashMap), dove ogni chiave è una stringa con la parola che viene contata e il valore è un numero intero che viene incrementato ogni volta che viene trovato un token della parola.

In Perl, incrementare un tale valore sarebbe banalmente facile:

$map{$word}++;

Ma in Java, è molto più complicato. Ecco come lo sto attualmente facendo:

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

Che ovviamente si basa sulla funzione di autoboxing nelle nuove versioni di Java. Mi chiedo se puoi suggerire un modo più efficiente di incrementare tale valore. Ci sono anche buone ragioni di prestazione per evitare il framework Collezioni e usare qualcos'altro?

Aggiornamento: ho fatto un test con diverse risposte. Vedi sotto.


Penso che sarebbe lo stesso per java.util.Hashtable.
jrudolph,

2
Certo, se fosse lo stesso, perché Hashtable è infatti una mappa.
whiskeysierra,

Java 8: Esempio computeIfAbsent: stackoverflow.com/a/37439971/1216775
akhil_mittal

Risposte:


367

Alcuni risultati del test

Ho ricevuto molte buone risposte a questa domanda - grazie gente - quindi ho deciso di eseguire alcuni test e capire quale metodo è effettivamente il più veloce. I cinque metodi che ho testato sono questi:

  • il metodo "ContainsKey" che ho presentato nella domanda
  • il metodo "TestForNull" suggerito da Aleksandar Dimitrov
  • il metodo "AtomicLong" suggerito da Hank Gay
  • il metodo "Trove" suggerito da jrudolph
  • il metodo "MutableInt" suggerito da phax.myopenid.com

Metodo

Ecco cosa ho fatto ...

  1. creato cinque classi identiche ad eccezione delle differenze mostrate di seguito. Ogni classe doveva eseguire un'operazione tipica dello scenario che ho presentato: aprire un file da 10 MB e leggerlo, quindi eseguire un conteggio di frequenza di tutti i token di parole nel file. Dal momento che ciò ha richiesto in media solo 3 secondi, l'ho fatto eseguire il conteggio delle frequenze (non l'I / O) 10 volte.
  2. ha cronometrato il ciclo di 10 iterazioni ma non l'operazione di I / O e ha registrato il tempo totale impiegato (in secondi di clock) essenzialmente utilizzando il metodo Ian Darwin nel libro di cucina Java .
  3. ha eseguito tutti e cinque i test in serie, e poi lo ha fatto altre tre volte.
  4. media dei quattro risultati per ciascun metodo.

risultati

Presenterò prima i risultati e il codice seguente per coloro che sono interessati.

Il metodo ContainsKey era, come previsto, il più lento, quindi fornirò la velocità di ciascun metodo rispetto alla velocità di quel metodo.

  • ContieneKey: 30.654 secondi (baseline)
  • AtomicLong: 29.780 secondi (1.03 volte più veloce)
  • TestForNull: 28.804 secondi (1,06 volte più veloce)
  • Trove: 26.313 secondi (1,16 volte più veloce)
  • MutableInt: 25.747 secondi (1,19 volte più veloce)

conclusioni

Sembrerebbe che solo il metodo MutableInt e il metodo Trove siano significativamente più veloci, in quanto solo loro danno un aumento delle prestazioni di oltre il 10%. Tuttavia, se il threading è un problema, AtomicLong potrebbe essere più attraente degli altri (non ne sono davvero sicuro). Ho anche eseguito TestForNull con finalvariabili, ma la differenza era trascurabile.

Si noti che non ho profilato l'utilizzo della memoria nei diversi scenari. Sarei felice di sapere da chiunque abbia buone intuizioni su come i metodi MutableInt e Trove potrebbero influenzare l'utilizzo della memoria.

Personalmente, trovo il metodo MutableInt il più interessante, dal momento che non richiede il caricamento di classi di terze parti. Quindi, a meno che non scopra problemi, è il modo più probabile che vada.

Il codice

Ecco il codice cruciale di ogni metodo.

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

Raccolta

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
Ottimo lavoro, ben fatto. Un commento secondario: la chiamata putIfAbsent () nel codice AtomicLong crea un'istanza di un nuovo AtomicLong (0) anche se uno è già nella mappa. Se lo ottimizzi per utilizzare if (map.get (key) == null), probabilmente otterrai un miglioramento nei risultati di questi test.
Leigh Caldwell,

2
Di recente ho fatto la stessa cosa con un approccio simile a MutableInt. Sono felice di sapere che è stata la soluzione ottimale (ho pensato che fosse, senza fare alcun test).
Kip

Mi fa piacere sapere che sei più veloce di me, Kip. ;-) Fammi sapere se scopri qualche inconveniente di questo approccio.
Gregory,

4
Nel caso Atomic Long non sarebbe più efficiente farlo in un solo passaggio (quindi hai solo 1 operazione get costosa invece di 2) "map.putIfAbsent (word, new AtomicLong (0)). IncrementAndGet ();"
smartnut007,

1
@gregory hai considerato Java 8 freq.compute(word, (key, count) -> count == null ? 1 : count + 1)? Internamente esegue una ricerca con hash in meno rispetto a quella containsKey, sarebbe interessante vedere come si confronta con gli altri, a causa della lambda.
TWiStErRob

255

Ora c'è un modo più breve con Java 8 usando Map::merge.

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

Cosa fa:

  • se la chiave non esiste, inserisci 1 come valore
  • altrimenti sommare 1 al valore associato alla chiave

Maggiori informazioni qui .


ami sempre java 8. È atomico? o dovrei circondarlo con un sincronizzato?
Tiina,

4
questo non ha funzionato per me, ma ha map.merge(key, 1, (a, b) -> a + b); fatto
rugger

2
Le caratteristiche di @Tiina Atomicity sono specifiche dell'implementazione, cfr. the docs : "L'implementazione predefinita non fornisce garanzie sulla sincronizzazione o sulle proprietà di atomicità di questo metodo. Qualsiasi implementazione che fornisca garanzie di atomicità deve sovrascrivere questo metodo e documentarne le proprietà di concorrenza. In particolare, tutte le implementazioni dell'interfaccia secondaria ConcurrentMap devono documentare se la funzione viene applicata una volta atomicamente solo se il valore non è presente. "
jensgram,

2
Per groovy, non accetterebbe Integer::sumcome BiFunction, e non piaceva a @russter rispondere nel modo in cui è stato scritto. Questo ha funzionato per meMap.merge(key, 1, { a, b -> a + b})
jookyone,

2
@russter, so che il tuo commento è stato più di un anno fa, ma ti capita di ricordare perché non ha funzionato per te? Hai ricevuto un errore di compilazione o il valore non è stato incrementato?
Paul

44

Una piccola ricerca nel 2016: https://github.com/leventov/java-word-count , codice sorgente benchmark

Risultati migliori per metodo (più piccolo è meglio):

                 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

Risultati tempo \ spazio:


2
Grazie, è stato davvero utile. Sarebbe bello aggiungere il benchmark Multiset di Guava (ad esempio, HashMultiset) al benchmark.
Cabad,

34

Google Guava è tuo amico ...

... almeno in alcuni casi. Hanno questa bella AtomicLongMap . Particolarmente piacevole, perché si tratta di lunghi come valore nella vostra mappa.

Per esempio

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

Inoltre è possibile aggiungere più di 1 al valore:

map.getAndAdd(word, 112L); 

7
AtomicLongMap#getAndAddaccetta una longclasse primitiva e non la classe wrapper; non ha senso farlo new Long(). Ed AtomicLongMapè un tipo parametrizzato; avresti dovuto dichiararlo come AtomicLongMap<String>.
Helder Pereira,

32

@Hank Gay

Come seguito al mio commento (piuttosto inutile): Trove sembra la strada da percorrere. Se, per qualsiasi motivo, si voleva rimanere con il JDK standard ConcurrentMap e AtomicLong possono rendere il codice un piccolo po 'più bello, anche se YMMV.

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

lascerà 1come valore nella mappa per foo. Realisticamente, una maggiore simpatia per il threading è tutto ciò che questo approccio deve raccomandare.


9
PutIfAbsent () restituisce il valore. Potrebbe essere un grande miglioramento memorizzare il valore restituito in una variabile locale e usarlo per incrementAndGet () anziché chiamare di nuovo get.
smartnut007,

putIfAbsent può restituire un valore null se la chiave specificata non è già associata a un valore all'interno di Map, quindi farei attenzione a utilizzare il valore restituito. 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);

Ed è così che si incrementa un valore con un semplice codice.

Beneficiare:

  • Non è necessario aggiungere una nuova classe o utilizzare un altro concetto di int mutabile
  • Non fare affidamento su nessuna libreria
  • Facile capire cosa sta succedendo esattamente (non troppa astrazione)

Svantaggio:

  • La mappa hash verrà cercata due volte per get () e put (). Quindi non sarà il codice più performante.

Teoricamente, una volta chiamato get (), sai già dove mettere (), quindi non dovresti cercare di nuovo. Ma la ricerca nella mappa hash richiede in genere un tempo minimo che è possibile ignorare questo problema di prestazioni.

Ma se sei molto serio sul problema, sei un perfezionista, un altro modo è quello di utilizzare il metodo merge, questo è (probabilmente) più efficiente del precedente frammento di codice in quanto (teoricamente) cercherai una volta sulla mappa: (sebbene questo codice non è evidente a prima vista, è breve e performante)

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

Suggerimento: dovresti preoccuparti della leggibilità del codice più che del piccolo aumento delle prestazioni nella maggior parte del tempo. Se il primo frammento di codice è più semplice da comprendere, utilizzalo. Ma se riesci a capire bene la seconda, allora puoi anche provarci!


Il metodo getOfDefault non è disponibile in JAVA 7. Come posso ottenere questo in JAVA 7?
Tanvi,

1
Quindi potresti dover fare affidamento su altre risposte. Funziona solo in Java 8.
off99555

1
+1 per la soluzione di unione, questa sarà la funzione con le migliori prestazioni perché dovrai pagare solo 1 volta per il calcolo dell'hashcode (nel caso in cui la Mappa su cui la stai utilizzando supporti correttamente il metodo), invece di pagare potenzialmente per essa 3 orari
Ferrybig,

2
Utilizzando l'inferenza del metodo: map.merge (chiave, 1, intero :: somma)
earandap

25

È sempre una buona idea consultare la Google Collections Library per questo genere di cose. In questo caso un Multiset farà il trucco:

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

Esistono metodi simili a mappe per iterare chiavi / voci, ecc. Internamente l'implementazione attualmente utilizza un HashMap<E, AtomicInteger>, quindi non dovrai sostenere costi di boxe.


La risposta sopra deve riflettere la risposta di tovares. L'API è cambiata dalla sua pubblicazione (3 anni fa :))
Steve

Il count()metodo su un multiset viene eseguito in O (1) o O (n) time (worstcase)? I documenti non sono chiari su questo punto.
Adam Parkin,

Il mio algoritmo per questo tipo di cose: if (hasApacheLib (thing)) restituisce apacheLib; altrimenti se (hasOnGuava (cosa)) restituisce guava. Di solito non riesco a superare questi due passaggi. :)
digao_mb,

22

Dovresti essere consapevole del fatto che il tuo tentativo originale

int count = map.containsKey (word)? map.get (word): 0;

contiene due operazioni potenzialmente costose su una mappa, vale a dire containsKeye get. Il primo esegue un'operazione potenzialmente abbastanza simile al secondo, quindi stai facendo lo stesso lavoro due volte !

Se si guarda l'API per Map, le getoperazioni di solito ritornano nullquando la mappa non contiene l'elemento richiesto.

Nota che questo renderà una soluzione simile

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

pericoloso, dal momento che potrebbe produrre NullPointerExceptions. Dovresti controllare per nullprimo.

Si noti inoltre , e questo è molto importante, che HashMaps può contenere nullsper definizione. Quindi non tutti i resi nulldicono "non esiste tale elemento". A questo proposito, containsKeysi comporta in modo diverso dal getdire effettivamente se esiste un tale elemento. Fare riferimento all'API per i dettagli.

Nel tuo caso, tuttavia, potresti non voler distinguere tra un memorizzato nulle "noSuchElement". Se non vuoi permetterti nullpotresti preferire a Hashtable. L'utilizzo di una libreria wrapper come già proposto in altre risposte potrebbe essere una soluzione migliore al trattamento manuale, a seconda della complessità dell'applicazione.

Per completare la risposta (e ho dimenticato di inserirlo inizialmente, grazie alla funzione di modifica!), Il modo migliore per farlo in modo nativo, è quello di getinserire una finalvariabile, controllare nulle putricollegarla con a 1. La variabile dovrebbe essere finalperché è immutabile comunque. Il compilatore potrebbe non aver bisogno di questo suggerimento, ma è più chiaro in questo modo.

mappa HashMap finale = generateRandomHashMap ();
final Object key = fetchSomeKey ();
numero intero finale i = map.get (chiave);
if (i! = null) {
    map.put (i + 1);
} altro {
    // fare qualcosa
}

Se non vuoi fare affidamento sull'autoboxing, dovresti invece dire qualcosa del genere map.put(new Integer(1 + i.getValue()));.


Per evitare il problema dei valori iniziali non mappati / null in groovy, finisco per fare :unts.put (chiave, (unts.get (chiave)?: 0) + 1) // versione troppo complessa di ++
Joe Atzberger

2
O, più semplicemente: count = [:]. WithDefault {0} // ++ away
Joe Atzberger

18

Un altro modo sarebbe creare un intero mutabile:

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 ();
}

ovviamente questo implica la creazione di un oggetto aggiuntivo, ma il sovraccarico rispetto alla creazione di un numero intero (anche con Integer.valueOf) non dovrebbe essere così tanto.


5
Non vuoi avviare MutableInt su 1 la prima volta che lo metti sulla mappa?
Tom Hawtin - tackline il

5
La lingua comune di Apache ha già un MutableInt scritto per te.
SingleShot

11

È possibile utilizzare il metodo computeIfAbsentMap nell'interfaccia fornita in Java 8 .

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]

Il metodo computeIfAbsentcontrolla se la chiave specificata è già associata o meno a un valore? Se nessun valore associato, tenta di calcolarne il valore utilizzando la funzione di mappatura fornita. In ogni caso restituisce il valore corrente (esistente o calcolato) associato alla chiave specificata o null se il valore calcolato è null.

In una nota a margine se si verifica una situazione in cui più thread aggiornano una somma comune, è possibile dare un'occhiata alla classe LongAdder. In un'alta contesa, il throughput atteso di questa classe è significativamente più alto di AtomicLong, a spese di un maggiore consumo di spazio.


perché concurrentHashmap e AtomicLong?
ealeon,

7

La rotazione della memoria può essere un problema qui, dal momento che ogni inscatolamento di un int maggiore o uguale a 128 provoca un'allocazione di oggetti (vedi Integer.valueOf (int)). Sebbene il garbage collector gestisca in modo molto efficiente oggetti di breve durata, le prestazioni ne risentiranno in parte.

Se sai che il numero di incrementi effettuati supererà ampiamente il numero di chiavi (= parole in questo caso), considera invece l'uso di un supporto int. Phax ha già presentato il codice per questo. Eccolo di nuovo, con due modifiche (la classe del titolare ha reso statico e il valore iniziale impostato su 1):

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();
}

Se hai bisogno di prestazioni estreme, cerca un'implementazione di Map che è direttamente su misura per i tipi di valore primitivi. jrudolph ha menzionato GNU Trove .

A proposito, un buon termine di ricerca per questo argomento è "istogramma".


5

Invece di chiamare IncludesKey () è più veloce chiamare map.get e verificare se il valore restituito è nullo o meno.

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

3

Sei sicuro che si tratti di un collo di bottiglia? Hai fatto qualche analisi delle prestazioni?

Prova a utilizzare il profiler NetBeans (è gratuito e integrato in NB 6.1) per esaminare gli hotspot.

Infine, un aggiornamento JVM (diciamo da 1.5-> 1.6) è spesso un booster economico. Anche un aggiornamento del numero di build può fornire buone prestazioni. Se si esegue Windows e si tratta di un'applicazione di classe server, utilizzare -server sulla riga comandi per utilizzare JVM Server Hotspot. Su macchine Linux e Solaris questo viene rilevato automaticamente.


3

Esistono un paio di approcci:

  1. Utilizza un algoritmo Bag come i set contenuti in Google Collections.

  2. Crea un contenitore modificabile che puoi utilizzare nella Mappa:


    class My{
        String word;
        int count;
    }

E usa put ("word", new My ("Word")); Quindi è possibile verificare se esiste e incrementare quando si aggiunge.

Evita di implementare la tua soluzione utilizzando gli elenchi, perché se ottieni la ricerca e l'ordinamento di innerloop, la tua performance puzzerà. La prima soluzione HashMap è in realtà abbastanza veloce, ma probabilmente una soluzione simile a quella trovata in Google Collections è migliore.

Il conteggio delle parole tramite Google Collections è simile al seguente:



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


L'uso di HashMultiset è abbastanza elegante, perché un algoritmo bag è proprio quello che ti serve per contare le parole.


3

Penso che la tua soluzione sarebbe il modo standard, ma - come hai notato te stesso - probabilmente non è il modo più veloce possibile.

Puoi guardare GNU Trove . Questa è una libreria che contiene tutti i tipi di raccolte primitive veloci. Il tuo esempio userebbe un TObjectIntHashMap che ha un metodo AdjustOrPutValue che fa esattamente quello che vuoi.


Il collegamento a TObjectIntHashMap è interrotto. Questo è il link corretto: trove4j.sourceforge.net/javadocs/gnu/trove/map/…
Erel Segal-Halevi,

Grazie, Erel, ho corretto il collegamento.
jrudolph,

3

Una variante dell'approccio MutableInt che potrebbe essere ancora più veloce, se un po 'di un hack, è usare un array int a elemento singolo:

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];

Sarebbe interessante se potessi ripetere i test delle prestazioni con questa variazione. Potrebbe essere il più veloce.


Modifica: il modello sopra ha funzionato bene per me, ma alla fine ho cambiato per usare le raccolte di Trove per ridurre la dimensione della memoria in alcune mappe molto grandi che stavo creando - e come bonus è stato anche più veloce.

Una caratteristica davvero interessante è che la TObjectIntHashMapclasse ha una sola adjustOrPutValuechiamata che, a seconda che esista già un valore in quella chiave, inserirà un valore iniziale o incrementerà il valore esistente. Questo è perfetto per incrementare:

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

3

HashMultiset di Google Collections:
- abbastanza elegante da usare
- ma consuma CPU e memoria

La cosa migliore sarebbe avere un metodo del tipo: Entry<K,V> getOrPut(K); (elegante ea basso costo)

Tale metodo calcolerà l'hash e l'indice solo una volta, quindi potremmo fare ciò che vogliamo con la voce (sostituire o aggiornare il valore).

Più elegante:
- prendi a HashSet<Entry>
- estendilo in modo da get(K)inserire una nuova voce, se necessario
- La voce potrebbe essere il tuo oggetto.
->(new MyHashSet()).get(k).increment();


3

Abbastanza semplice, basta usare la funzione integrata Map.javacome segue

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

Questo non incrementa il valore, imposta semplicemente il valore corrente o 0 se nessun valore è stato assegnato alla chiave.
siegi

Puoi incrementare il valore di ++... OMG, è così semplice. @siegi
sudoz,

Per la cronaca: ++non funziona da nessuna parte in questa espressione perché è necessaria una variabile come suo operando ma ci sono solo valori. La tua aggiunta di + 1opere però. Ora la tua soluzione è la stessa della risposta off99555s .
siegi,

2

"put" necessita "get" (per garantire che non vi siano chiavi duplicate).
Quindi fai direttamente un "put",
e se c'era un valore precedente, fai un'aggiunta:

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
}

Se il conteggio inizia da 0, quindi aggiungi 1: (o qualsiasi altro valore ...)

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
}

Avviso: questo codice non è thread-safe. Usalo per costruire quindi usa la mappa, non per aggiornarla contemporaneamente.

Ottimizzazione: in un ciclo, mantieni il vecchio valore per diventare il nuovo valore del ciclo successivo.

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

I vari wrapper primitivi, ad esempio, Integersono immutabili, quindi non c'è davvero un modo più conciso di fare ciò che stai chiedendo a meno che tu non possa farlo con qualcosa come AtomicLong . Posso provarlo tra un minuto e aggiornarlo. A proposito, Hashtable è una parte del Framework Collezioni .


1

Vorrei usare Apache Collections Lazy Map (per inizializzare i valori su 0) e utilizzare MutableIntegers da Apache Lang come valori in quella mappa.

Il costo più grande è dover ricollegare la mappa due volte nel tuo metodo. Nel mio devi farlo solo una volta. Basta ottenere il valore (verrà inizializzato se assente) e incrementarlo.


1

La struttura di dati della libreria Java funzionaleTreeMap ha un updatemetodo nell'ultima testata del tronco:

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

Esempio di utilizzo:

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());}}

Questo programma stampa "2".


1

@Vilmantas Baranauskas: A proposito di questa risposta, vorrei commentare se avessi i punti rep, ma non lo faccio. Volevo notare che la classe Counter definita non è thread-safe in quanto non è sufficiente sincronizzare inc () senza sincronizzare value (). Altri thread che chiamano value () non sono garantiti per vedere il valore a meno che non sia stata stabilita una relazione prima dell'aggiornamento con l'aggiornamento.


Se vuoi fare riferimento alla risposta di qualcuno, usa @ [Nome utente] in alto, ad es. @Vilmantas Baranauskas <Il contenuto va qui>
Hank Gay

Ho fatto quella modifica per ripulirlo.
Alex Miller,

1

Non so quanto sia efficace, ma funziona anche il codice seguente. Devi definire BiFunctiona all'inizio. Inoltre, puoi fare molto di più che incrementare con questo metodo.

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

l'uscita è

3
1

1

Se si utilizzano le raccolte Eclipse , è possibile utilizzare a HashBag. Sarà l'approccio più efficiente in termini di utilizzo della memoria e funzionerà bene anche in termini di velocità di esecuzione.

HashBagè supportato da un oggetto MutableObjectIntMapche memorizza ints primitivi anziché Counteroggetti. Ciò riduce il sovraccarico di memoria e migliora la velocità di esecuzione.

HashBag fornisce l'API di cui avresti bisogno poiché è a Collection che ti consente anche di eseguire una query per il numero di occorrenze di un elemento.

Ecco un esempio dalle collezioni Eclipse Kata .

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

Nota: sono un committer per le raccolte Eclipse.


1

Suggerisco di usare Java 8 Map :: compute (). Considera anche il caso in cui non esiste una chiave.

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

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

-2

Dato che molte persone cercano negli argomenti Java le risposte di Groovy, ecco come puoi farlo in Groovy:

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

Il modo semplice e facile in Java 8 è il seguente:

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

-3

Spero di capire correttamente la tua domanda, vengo a Java da Python in modo da poter entrare in empatia con la tua lotta.

se hai

map.put(key, 1)

tu lo faresti

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

Spero che sia di aiuto!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.