Alguns resultados do teste
Eu recebi muitas respostas boas para essa pergunta - obrigado pessoal -, então decidi executar alguns testes e descobrir qual método é realmente mais rápido. Os cinco métodos que testei são os seguintes:
- o método "ContainsKey" que apresentei na pergunta
- o método "TestForNull" sugerido por Aleksandar Dimitrov
- o método "AtomicLong" sugerido por Hank Gay
- o método "Trove" sugerido por jrudolph
- o método "MutableInt" sugerido por phax.myopenid.com
Método
Aqui está o que eu fiz ...
- criou cinco classes que eram idênticas, exceto pelas diferenças mostradas abaixo. Cada classe teve que executar uma operação típica do cenário que apresentei: abrindo um arquivo de 10 MB e lendo-o e executando uma contagem de frequência de todos os tokens de palavras no arquivo. Como isso levou uma média de apenas 3 segundos, eu fiz a contagem de frequência (não a E / S) 10 vezes.
- cronometrou o loop de 10 iterações, mas não a operação de E / S, e registrou o tempo total gasto (em segundos do relógio), essencialmente usando o método de Ian Darwin no Java Cookbook .
- realizou todos os cinco testes em série e depois fez isso mais três vezes.
- calculou a média dos quatro resultados para cada método.
Resultados
Vou apresentar os resultados primeiro e o código abaixo para quem estiver interessado.
O método ContainsKey foi, como esperado, o mais lento, portanto, darei a velocidade de cada método em comparação com a velocidade desse método.
- ContainsKey: 30.654 segundos (linha de base)
- AtomicLong: 29.780 segundos (1,03 vezes mais rápido)
- TestForNull: 28.804 segundos (1,06 vezes mais rápido)
- Trove: 26.313 segundos (1,16 vezes mais rápido)
- MutableInt: 25.747 segundos (1,19 vezes mais rápido)
Conclusões
Parece que apenas o método MutableInt e o método Trove são significativamente mais rápidos, pois apenas eles oferecem um aumento de desempenho superior a 10%. No entanto, se o threading for um problema, o AtomicLong pode ser mais atraente que os outros (não tenho muita certeza). Também executei o TestForNull comfinal
variáveis, mas a diferença era insignificante.
Observe que não analisei o uso de memória nos diferentes cenários. Eu ficaria feliz em ouvir de alguém que tenha boas idéias sobre como os métodos MutableInt e Trove provavelmente afetarão o uso da memória.
Pessoalmente, acho o método MutableInt o mais atraente, pois não requer o carregamento de nenhuma classe de terceiros. Então, a menos que eu descubra problemas, é assim que provavelmente vou.
O código
Aqui está o código crucial de cada método.
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();
}