Algunos resultados de la prueba
He recibido muchas buenas respuestas a esta pregunta, gracias amigos, así que decidí realizar algunas pruebas y descubrir qué método es realmente el más rápido. Los cinco métodos que probé son estos:
- El método "ContainsKey" que presenté en la pregunta
- El método "TestForNull" sugerido por Aleksandar Dimitrov
- El método "AtomicLong" sugerido por Hank Gay
- el método "Trove" sugerido por jrudolph
- El método "MutableInt" sugerido por phax.myopenid.com
Método
Esto es lo que hice ...
- creó cinco clases que eran idénticas, excepto por las diferencias que se muestran a continuación. Cada clase tuvo que realizar una operación típica del escenario que presenté: abrir un archivo de 10 MB y leerlo, luego realizar un recuento de frecuencia de todos los tokens de palabras en el archivo. Como esto tomó un promedio de solo 3 segundos, tuve que realizar el conteo de frecuencia (no la E / S) 10 veces.
- cronometró el ciclo de 10 iteraciones pero no la operación de E / S y registró el tiempo total empleado (en segundos de reloj) esencialmente usando el método de Ian Darwin en el Java Cookbook .
- realizó las cinco pruebas en serie, y luego lo hizo otras tres veces.
- promedió los cuatro resultados para cada método.
Resultados
Presentaré los resultados primero y el código a continuación para aquellos que estén interesados.
El método ContainsKey fue, como se esperaba, el más lento, por lo que daré la velocidad de cada método en comparación con la velocidad de ese método.
- Contiene clave : 30.654 segundos (línea de base)
- AtomicLong: 29.780 segundos (1.03 veces más rápido)
- TestForNull: 28.804 segundos (1.06 veces más rápido)
- Trove: 26.313 segundos (1.16 veces más rápido)
- MutableInt: 25.747 segundos (1.19 veces más rápido)
Conclusiones
Parece que solo el método MutableInt y el método Trove son significativamente más rápidos, ya que solo dan un aumento de rendimiento de más del 10%. Sin embargo, si el subproceso es un problema, AtomicLong podría ser más atractivo que los demás (no estoy realmente seguro). También ejecuté TestForNull con final
variables, pero la diferencia fue insignificante.
Tenga en cuenta que no he perfilado el uso de memoria en los diferentes escenarios. Me alegraría saber de cualquiera que tenga una buena idea de cómo los métodos MutableInt y Trove podrían afectar el uso de la memoria.
Personalmente, considero que el método MutableInt es el más atractivo, ya que no requiere cargar ninguna clase de terceros. Entonces, a menos que descubra problemas con él, esa es la forma en que es más probable que vaya.
El código
Aquí está el código crucial de cada método.
Contiene clave
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();
}