Calculateur efficace de la fréquence des mots en Java

Calculateur de fréquence de mots efficace en Java

1. Vue d'ensemble

Dans ce tutoriel, nous montrerons différentes manières de mettre en œuvre un compteur de mots en Java.

2. Implémentations de compteur

Commençons par calculer simplement le nombre de mots des mots de ce tableau:

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

Si nous voulons traiter de gros fichiers, nous devons opter pour d'autres options décriteshere.

2.1. Map avecIntegers

L'une des solutions les plus simples serait de créer unMap, de stocker les mots sous forme de clés et le nombre d'occurrences sous forme de valeurs:

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

Nous avons simplement utilisé la méthode pratiquecompute deMap qui incrémente le compteur ou l'initialise à 1 si la clé n'est pas présente.

Cependant,this method of creating counter isn’t efficient as Integer is immutable, so every time when we increment the counter, we create a new Integer object.

2.2. API de flux

Tirons maintenant parti de l'API Java 8 Stream, desStreams parallèles et du collecteurgroupingBy():

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

De même, nous pourrions utiliser unparallelStream:

@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 avec un tableauInteger

Ensuite, utilisons unMap qui encapsule un compteur dans un tableauInteger utilisé comme valeur:

@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]++;
    }
}

Notez comment nous avons créé un simpleHashMap avecint arrays comme valeurs.

Dans la méthodecounterWithPrimitiveArray, en itérant sur chaque valeur du tableau, nous:

  • invoquer unget sur lecounterMap en passant le nom du pays comme clé

  • vérifier si une clé était déjà présente ou non. Si l'entrée est déjà présente, nous créons une nouvelle instance de tableau primitif entier avec un seul «1». Si l'entrée est absente, nous incrémentons la valeur du compteur présente dans le tableau

Cette méthode est meilleure que l'implémentation du wrapper -as it creates fewer objects.

2.4. Map avec unMutableInteger

Ensuite, créons un objet wrapper qui incorpore un compteur entier primitif comme ci-dessous:

private static class MutableInteger {
    int count = 1;

    public void increment() {
        this.count++;
    }

    // getter and setter
}

Voyons comment nous pouvons utiliser la classe ci-dessus comme compteur:

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

Dans la méthodemapWithMutableInteger, tout en itérant sur chaque pays du tableauCOUNTRY_NAMES, nous:

  • invoquer un get sur lescounterMap en passant le nom du pays comme clé

  • vérifiez si la clé est déjà présente ou non. Si une entrée est absente, nous créons une instance deMutableInteger qui définit la valeur du compteur sur 1. On incrémente la valeur du compteur présente dans lesMutableInteger si le pays est présent sur la carte

Cette méthode de création de compteur est meilleure que la précédente -as we’re reusing the same MutableInteger and thereby creating fewer objects.

C'est ainsi qu'Apache CollectionsHashMultiSet fonctionne où il incorpore unHashMap avec la valeurMutableInteger en interne.

3. Analyse de performance

Voici le graphique qui compare les performances de chacune des méthodes répertoriées ci-dessus. image

Le graphique ci-dessus est créé à l'aide de JMH et voici le code qui a créé les statistiques ci-dessus:

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. Conclusion

Dans cet article rapide, nous avons illustré différentes manières de créer des compteurs de mots en utilisant Java.

L'implémentation de ces exemples peut être trouvée dansthe GitHub project - c'est un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.