Рубрики
Без рубрики

Эффективный калькулятор частоты слов в Java

Изучите различные способы подсчета слов на Java и узнайте, как они работают.

Автор оригинала: baeldung.

1. Обзор

В этом учебнике мы покажем различные способы реализации счетчика слов на Java.

2. Встречные реализации

Начнем с простого расчета слова кол слов в этом массиве:

static String[] COUNTRY_NAMES 
  = { "China", "Australia", "India", "USA", "USSR", "UK", "China", 
  "France", "Poland", "Austria", "India", "USA", "Egypt", "China" };

Если мы хотим обрабатывать огромные файлы, мы должны пойти на другие варианты, описанные здесь .

2.1. Карта с интеграторами

Одним из самых простых решений было бы создание Карта , храните слова в качестве ключей и количество случаев в качестве значений:

Map counterMap = 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() {
    Map counterMap = 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() {
    Map counterMap = 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() {
    Map counterMap = 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() {
    Map counterMap = 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 и вот код, который создал статистику выше:

Map counterMap = 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 основе проекта, поэтому она должна быть легко импортировать и работать как есть.