Ефективен калкулатор на честотата на думи в Java

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());

Просто използвахме удобния метод за изчисление на Map, който увеличава брояча или го инициализира с 1, ако ключът не присъства.

Въпреки това, този метод за създаване брояч не е ефективен като цяло число е непроменяема, така че всеки път, когато ние увеличаваме брояча, ние създаваме нова Integer обект.

2.2. API за поток

Сега нека използваме Java 8 Stream API, паралелни потоци и колектора groupingBy ():

@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. Карта с цяло число масив

След това нека използваме Map, която обгръща брояч в масив Integer, използван като стойност:

@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 като стойности.

В метода counterWithPrimitiveArray , докато итерираме над всяка стойност на масива, ние:

  • извикайте get на counterMap, като предадете името на държавата като ключ
  • проверете дали ключът вече е присъствал или не. Ако записът вече е налице, ние създаваме нов екземпляр на примитивен целочислен масив с единичен „1“. Ако записът отсъства, ние увеличаваме стойността на брояча, присъстваща в масива

Този метод е по-добър от изпълнението на обвивката - тъй като създава по-малко обекти.

2.4. Карта с изменяемо цяло число

След това нека създадем обект-обвивка, който вгражда примитивен брояч като цяло:

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(); } }

В метода mapWithMutableInteger , докато итерираме над всяка държава в масива COUNTRY_NAMES , ние:

  • извикайте get на counterMap, като предадете името на държавата като ключ
  • проверете дали ключът вече присъства или не. Ако запис липсва, ние създаваме екземпляр на MutableInteger, който задава стойността на брояча като 1. Увеличаваме стойността на брояча, присъстваща в MutableInteger, ако държавата присъства на картата

Този метод за създаване на брояч е по-добър от предишния - тъй като използваме повторно същия MutableInteger и по този начин създаваме по-малко обекти.

Ето как работи Apache Collections HashMultiSet, когато вътрешно вгражда HashMap със стойност като MutableInteger .

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, така че трябва да е лесно да се импортира и да се изпълнява както е.