1. Обзор
В этом учебнике мы покажем различные способы реализации счетчика слов на Java.
2. Встречные реализации
Начнем с простого расчета слова кол слов в этом массиве:
static String[] COUNTRY_NAMES = { "China", "Australia", "India", "USA", "USSR", "UK", "China", "France", "Poland", "Austria", "India", "USA", "Egypt", "China" };
Если мы хотим обрабатывать огромные файлы, мы должны пойти на другие варианты, описанные здесь .
2.1. Карта с интеграторами
Одним из самых простых решений было бы создание Карта , храните слова в качестве ключей и количество случаев в качестве значений:
MapcounterMap = new HashMap<>(); for (String country : COUNTRY_NAMES) { counterMap.compute(country, (k, v) -> v == null ? 1 : v + 1); } assertEquals(3, counterMap.get("China").intValue()); assertEquals(2, counterMap.get("India").intValue());
Мы просто использовали Карта Удобный вычислить метод, который приращения счетчика или инициализирует его с 1, если ключ не присутствует.
Тем не менее, этот метод создания счетчика не эффективен, как Интегер неизменяем, поэтому каждый раз, когда мы увеличиваем счетчик, мы создаем новую Интегер объект.
2.2. API потока
Теперь давайте использовать Java 8 Stream API, параллельно Потоки , и группировкаБай () коллектор:
@Test public void whenMapWithLambdaAndWrapperCounter_runsSuccessfully() { MapcounterMap = new HashMap<>(); Stream.of(COUNTRY_NAMES) .collect(Collectors.groupingBy(k -> k, ()-> counterMap, Collectors.counting()); assertEquals(3, counterMap.get("China").intValue()); assertEquals(2, counterMap.get("India").intValue()); }
Аналогичным образом, мы могли бы использовать параллельныйстрим :
@Test public void whenMapWithLambdaAndWrapperCounter_runsSuccessfully() { MapcounterMap = new HashMap<>(); Stream.of(COUNTRY_NAMES).parallel() .collect(Collectors.groupingBy(k -> k, ()-> counterMap, Collectors.counting()); assertEquals(3, counterMap.get("China").intValue()); assertEquals(2, counterMap.get("India").intValue()); }
2.3. Карта с интегратором Array
Далее, давайте использовать Карта который обертывает счетчик в Интегер массив, используемый в качестве значения:
@Test public void whenMapWithPrimitiveArrayCounter_runsSuccessfully() { MapcounterMap = new HashMap<>(); counterWithPrimitiveArray(counterMap); assertEquals(3, counterMap.get("China")[0]); assertEquals(2, counterMap.get("India")[0]); } private void counterWithPrimitiveArray(Map counterMap) { for (String country : COUNTRY_NAMES) { counterMap.compute(country, (k, v) -> v == null ? new int[] { 0 } : v)[0]++; } }
Обратите внимание, как мы создали простую HashMap с int массивы как ценности.
В счетчикСПримитивныйАррай метод, итерируя каждое значение массива, мы:
- вызвать получить на counterMap передавая название страны в качестве ключевого
- проверить, был ли ключ уже присутствует или нет. Если запись уже присутствует, мы создаем новый экземпляр примитивного массива целых с одним “1”. Если запись отсутствует, мы увеличиваем встречное значение, присутствующего в массиве
Этот метод лучше, чем реализация обертки – как он создает меньше объектов.
2.4. Карта с мутируемымinteger
Далее давайте создадим объект обертки, который встраивает примитивный счетчик стельки, как у нас ниже:
private static class MutableInteger { int count = 1; public void increment() { this.count++; } // getter and setter }
Давайте посмотрим, как мы можем использовать выше класса в качестве счетчика:
@Test public void whenMapWithMutableIntegerCounter_runsSuccessfully() { MapcounterMap = new HashMap<>(); mapWithMutableInteger(counterMap); assertEquals(3, counterMap.get("China").getCount()); assertEquals(2, counterMap.get("India").getCount()); } private void counterWithMutableInteger( Map counterMap) { for (String country : COUNTRY_NAMES) { counterMap.compute(country, (k, v) -> v == null ? new MutableInteger(0) : v).increment(); } }
В картаСМутируемыйInteger метод, в то время как итерирование по каждой стране в COUNTRY_NAMES массив, мы:
- вызвать получить на counterMap передавая название страны в качестве ключевого
- проверить, является ли ключ уже присутствует или нет. Если запись отсутствует, мы создаем экземпляр МутаблИнтегер который устанавливает встречное значение как 1. Мы увеличиваем встречное значение, присутствую МутаблИнтегер если страна присутствует на карте
Этот метод создания счетчика лучше предыдущего – как мы повторно же МутаблИнтегер и тем самым создавая меньше объектов.
Вот как Apache Коллекции HashMultiSet работает там, где он встраивает HashMap со значением, как МутаблИнтегер внутренне.
3. Анализ производительности
Вот диаграмма, которая сравнивает производительность каждого метода, перечисленного выше.
Выше диаграмма создается с помощью JMH и вот код, который создал статистику выше:
MapcounterMap = new HashMap<>(); Map counterMutableIntMap = new HashMap<>(); Map counterWithIntArrayMap = new HashMap<>(); Map counterWithLongWrapperMap = new HashMap<>(); @Benchmark public void wrapperAsCounter() { counterWithWrapperObject(counterMap); } @Benchmark public void lambdaExpressionWithWrapper() { counterWithLambdaAndWrapper(counterWithLongWrapperMap ); } @Benchmark public void parallelStreamWithWrapper() { counterWithParallelStreamAndWrapper(counterWithLongWrapperStreamMap); } @Benchmark public void mutableIntegerAsCounter() { counterWithMutableInteger(counterMutableIntMap); } @Benchmark public void mapWithPrimitiveArray() { counterWithPrimitiveArray(counterWithIntArrayMap); }
4. Заключение
В этой быстрой статье мы проиллюстрировали различные способы создания счетчиков слов с помощью Java.
Осуществление этих примеров можно найти в проект GitHub – это Maven основе проекта, поэтому она должна быть легко импортировать и работать как есть.