Quelques résultats de tests
J'ai obtenu beaucoup de bonnes réponses à cette question - merci les gens - j'ai donc décidé d'exécuter des tests et de déterminer la méthode la plus rapide. Les cinq méthodes que j'ai testées sont les suivantes:
- la méthode "ContainsKey" que j'ai présentée dans la question
- la méthode "TestForNull" proposée par Aleksandar Dimitrov
- la méthode "AtomicLong" proposée par Hank Gay
- la méthode "Trove" proposée par jrudolph
- la méthode "MutableInt" proposée par phax.myopenid.com
Méthode
Voici ce que j'ai fait ...
- créé cinq classes identiques à l'exception des différences ci-dessous. Chaque classe devait effectuer une opération typique du scénario que j'ai présenté: ouvrir un fichier de 10 Mo et le lire, puis effectuer un décompte de fréquence de tous les jetons de mot du fichier. Comme cela ne prenait en moyenne que 3 secondes, je l'ai fait effectuer le décompte de fréquences (pas les E / S) 10 fois.
- chronométré la boucle de 10 itérations mais pas l'opération d'E / S et enregistré le temps total pris (en secondes d'horloge) essentiellement en utilisant la méthode de Ian Darwin dans le Java Cookbook .
- effectué les cinq tests en série, puis l'a fait trois fois de plus.
- moyenne des quatre résultats pour chaque méthode.
Résultats
Je vais d'abord présenter les résultats et le code ci-dessous pour ceux qui sont intéressés.
La méthode ContainsKey était, comme prévu, la plus lente, je vais donc donner la vitesse de chaque méthode par rapport à la vitesse de cette méthode.
- ContainsKey: 30,654 secondes (ligne de base)
- AtomicLong: 29,780 secondes (1,03 fois plus rapide)
- TestForNull: 28 804 secondes (1,06 fois plus rapide)
- Trove: 26,313 secondes (1,16 fois plus rapide)
- MutableInt: 25,747 secondes (1,19 fois plus rapide)
Conclusions
Il semblerait que seules la méthode MutableInt et la méthode Trove soient significativement plus rapides, en ce qu'elles seules donnent une amélioration des performances de plus de 10%. Cependant, si le filetage est un problème, AtomicLong pourrait être plus attrayant que les autres (je ne suis pas vraiment sûr). J'ai également exécuté TestForNull avec des final
variables, mais la différence était négligeable.
Notez que je n'ai pas profilé l'utilisation de la mémoire dans les différents scénarios. Je serais heureux d'entendre tous ceux qui ont une bonne idée de la façon dont les méthodes MutableInt et Trove seraient susceptibles d'affecter l'utilisation de la mémoire.
Personnellement, je trouve la méthode MutableInt la plus intéressante, car elle ne nécessite aucun chargement de classes tierces. Donc, sauf si je découvre des problèmes, c'est la voie que je suis le plus susceptible d'aller.
Le code
Voici le code crucial de chaque méthode.
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();
Trove
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();
}