Effizienter Wortfrequenzrechner in Java

Effizienter Wortfrequenzrechner in Java

1. Überblick

In diesem Lernprogramm werden verschiedene Möglichkeiten zum Implementieren eines Wortzählers in Java gezeigt.

2. Gegenimplementierungen

Beginnen wir mit der einfachen Berechnung der Wortanzahl der Wörter in diesem Array:

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

Wenn wir große Dateien verarbeiten möchten, müssen wir andere Optionen wählen, die inhere beschrieben sind.

2.1. Map mitIntegers

Eine der einfachsten Lösungen wäre, einMapzu erstellen, Wörter als Schlüssel und die Anzahl der Vorkommen als Werte zu speichern:

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

Wir haben einfach die handlichecompute-Methode vonMapverwendet, mit der der Zähler erhöht oder mit 1 initialisiert wird, wenn der Schlüssel nicht vorhanden ist.

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. Stream-API

Nutzen wir nun die Java 8 Stream-API, die parallelenStreams und dengroupingBy()-Kollektor:

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

Ebenso könnten wir einparallelStream verwenden:

@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 mit einemInteger Array

Als nächstes verwenden wir einMap, das einen Zähler in einInteger-Array einschließt, das als Wert verwendet wird:

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

Beachten Sie, wie wir ein einfachesHashMap mitint arrays als Werten erstellt haben.

In dercounterWithPrimitiveArray-Methode werden beim Durchlaufen jedes Werts des Arrays:

  • Rufen Sie einget aufcounterMap auf, indem Sie den Ländernamen als Schlüssel übergeben

  • Überprüfen Sie, ob bereits ein Schlüssel vorhanden war. Wenn der Eintrag bereits vorhanden ist, erstellen wir eine neue Instanz des primitiven Integer-Arrays mit einer einzelnen „1“. Wenn der Eintrag fehlt, erhöhen wir den im Array vorhandenen Zählerwert

Diese Methode ist besser als die Wrapper-Implementierung -as it creates fewer objects.

2.4. Map mit einemMutableInteger

Als Nächstes erstellen wir ein Wrapper-Objekt, in das ein primitiver Ganzzahlzähler wie folgt eingebettet ist:

private static class MutableInteger {
    int count = 1;

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

    // getter and setter
}

Mal sehen, wie wir die obige Klasse als Zähler verwenden können:

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

Bei dermapWithMutableInteger-Methode werden beim Durchlaufen jedes Landes imCOUNTRY_NAMES-Array:

  • Rufen Sie einen get on thecounterMap auf, indem Sie den Ländernamen als Schlüssel übergeben

  • Überprüfen Sie, ob der Schlüssel bereits vorhanden ist. Wenn ein Eintrag fehlt, erstellen wir eine Instanz vonMutableInteger, die den Zählerwert auf 1 setzt. Wir erhöhen den inMutableInteger vorhandenen Zählerwert, wenn das Land auf der Karte vorhanden ist

Diese Methode zum Erstellen eines Zählers ist besser als die vorherige -as we’re reusing the same MutableInteger and thereby creating fewer objects.

So funktioniert Apache CollectionsHashMultiSet, bei demHashMap mit dem WertMutableInteger intern eingebettet werden.

3. Performance-Analyse

In der folgenden Tabelle wird die Leistung aller oben aufgeführten Methoden verglichen. image

Das obige Diagramm wird mit JMH erstellt. Hier ist der Code, mit dem die obigen Statistiken erstellt wurden:

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

In diesem kurzen Artikel haben wir verschiedene Möglichkeiten zum Erstellen von Wortzählern mit Java veranschaulicht.

Die Implementierung dieser Beispiele finden Sie inthe GitHub project - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.