Niektóre wyniki testu
Otrzymałem wiele dobrych odpowiedzi na to pytanie - dzięki, ludzie - więc postanowiłem przeprowadzić kilka testów i dowiedzieć się, która metoda jest rzeczywiście najszybsza. Pięć metod, które przetestowałem, to:
- metoda „ContainsKey”, którą przedstawiłem w pytaniu
- metoda „TestForNull” sugerowana przez Aleksandra Dymitrowa
- metoda „AtomicLong” sugerowana przez Hanka Gaya
- metoda „Trove” sugerowana przez jrudolpha
- metoda „MutableInt” sugerowana przez phax.myopenid.com
metoda
Oto co zrobiłem ...
- utworzono pięć klas, które były identyczne, z wyjątkiem różnic pokazanych poniżej. Każda klasa musiała wykonać operację typową dla przedstawionego przeze mnie scenariusza: otworzyć plik 10 MB i wczytać go, a następnie wykonać liczenie częstotliwości wszystkich tokenów słów w pliku. Ponieważ zajęło to średnio tylko 3 sekundy, kazałem mu wykonać liczenie częstotliwości (nie I / O) 10 razy.
- dokonał pomiaru pętli 10 iteracji, ale nie operacji I / O i zarejestrował całkowity czas (w sekundach zegarowych) zasadniczo przy użyciu metody Iana Darwina w książce kucharskiej Java .
- wykonał wszystkie pięć testów w szeregu, a następnie zrobił to jeszcze trzy razy.
- uśredniono cztery wyniki dla każdej metody.
Wyniki
Najpierw przedstawię wyniki, a poniżej kod dla zainteresowanych.
Metoda ContainsKey była, zgodnie z oczekiwaniami, najwolniejsza, dlatego podam prędkość każdej metody w porównaniu do prędkości tej metody.
- Zawiera klucz : 30,654 sekundy (linia bazowa)
- AtomicLong: 29,780 sekund (1,03 razy szybciej)
- TestForNull: 28,804 sekundy (1,06 razy szybciej)
- Trove: 26,313 sekund (1,16 razy szybciej)
- MutableInt: 25,747 sekund (1,19 razy szybciej)
Wnioski
Wydaje się, że tylko metoda MutableInt i metoda Trove są znacznie szybsze, ponieważ tylko one dają wzrost wydajności o ponad 10%. Jeśli jednak wątek stanowi problem, AtomicLong może być bardziej atrakcyjny niż inne (nie jestem do końca pewien). Uruchomiłem także TestForNull ze final
zmiennymi, ale różnica była znikoma.
Pamiętaj, że nie profilowałem użycia pamięci w różnych scenariuszach. Z przyjemnością dowiem się od każdego, kto ma dobry wgląd w to, w jaki sposób metody MutableInt i Trove mogłyby wpłynąć na wykorzystanie pamięci.
Osobiście uważam, że metoda MutableInt jest najbardziej atrakcyjna, ponieważ nie wymaga ładowania żadnych klas stron trzecich. Więc jeśli nie odkryję problemów z tym, najprawdopodobniej pójdę.
Kod
Oto kluczowy kod z każdej metody.
Zawiera klucz
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();
}