Некоторые результаты теста
Я получил много хороших ответов на этот вопрос - спасибо, ребята - поэтому я решил провести несколько тестов и выяснить, какой метод на самом деле самый быстрый. Вот пять методов, которые я протестировал:
- метод "ContainsKey", который я представил в вопрос
- метод "TestForNull", предложенный Александром Димитровым
- метод "AtomicLong", предложенный Хэнком Гаем
- метод "Trove", предложенный Джрудольфом
- метод "MutableInt", предложенный phax.myopenid.com
Метод
Вот что я сделал ...
- создал пять классов, которые были идентичны, за исключением различий, показанных ниже. Каждый класс должен был выполнить операцию, типичную для сценария, который я представил: открыть файл 10 МБ и прочитать его, затем выполнить подсчет частоты всех жетонов слов в файле. Так как это заняло в среднем всего 3 секунды, мне пришлось выполнять подсчет частоты (не I / O) 10 раз.
- рассчитал цикл из 10 итераций, но не , а не операцию ввода-вывода и записал общее время, затраченное (в секундах), по существу, используя метод Яна Дарвина в книге рецептов Java .
- выполнил все пять тестов подряд, а затем сделал это еще три раза.
- усреднил четыре результата для каждого метода.
Результаты
Сначала я представлю результаты и приведенный ниже код для тех, кому это интересно.
Метод ContainsKey оказался, как и ожидалось, самым медленным, поэтому я приведу скорость каждого метода по сравнению со скоростью этого метода.
- ContainsKey: 30,654 секунды (базовый уровень)
- AtomicLong: 29,780 секунд (в 1,03 раза быстрее)
- TestForNull: 28,804 секунды (в 1,06 раза быстрее)
- Скорость: 26,313 секунд (в 1,16 раза быстрее)
- MutableInt: 25,747 секунд (в 1,19 раза быстрее)
Выводы
Может показаться, что только метод MutableInt и метод Trove значительно быстрее, поскольку только они дают прирост производительности более чем на 10%. Однако, если многопоточность является проблемой, AtomicLong может быть более привлекательным, чем другие (я не совсем уверен). Я также запустил TestForNull с final
переменными, но разница была незначительной.
Обратите внимание, что я не профилировал использование памяти в различных сценариях. Я был бы рад услышать от любого, кто имеет хорошее представление о том, как методы MutableInt и Trove могут повлиять на использование памяти.
Лично я считаю метод MutableInt наиболее привлекательным, поскольку он не требует загрузки сторонних классов. Поэтому, если я не обнаружу проблем с этим, я, скорее всего, пойду по этому пути.
код
Вот ключевой код каждого метода.
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();
}