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 ...
- 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.
- 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 .
- ha eseguito tutti e cinque i test in serie, e poi lo ha fatto altre tre volte.
- 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 final
variabili, 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();
}