1概要

このチュートリアルでは、単語カウンタをJavaで実装するさまざまな方法を紹介します。


2カウンター実装

この配列の単語数を単純に計算することから始めましょう。

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

巨大なファイルを処理したい場合は、

ここ

に記載されている他のオプションを選択する必要があります。


2.1.

Map



Integers


最も簡単な解決策の1つは、

Map

を作成し、単語をキーとして、出現回数を値として格納することです。

Map<String, Integer> 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

の便利な

compute

メソッドを使用して、カウンタをインクリメントするか、キーが存在しない場合は1で初期化します。

ただし、

Integer

は不変であるため、** このカウンタ作成方法は効率的ではありません。したがって、カウンタをインクリメントするたびに新しい

Integer

オブジェクトが作成されます。


2.2. ストリームAPI

それでは、Java 8 Stream API、parallel

Streams

、および


groupingBy

()

コレクターを活用しましょう。

@Test
public void whenMapWithLambdaAndWrapperCounter__runsSuccessfully() {
    Map<String, Long> 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());
}

同様に、

parallelStream

を使用できます。

@Test
public void whenMapWithLambdaAndWrapperCounter__runsSuccessfully() {
    Map<String, Long> 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.

Integer

配列を持つ

Map


次に、値として使用される

Integer

配列内にカウンターをラップする

Map

を使用しましょう。

@Test
public void whenMapWithPrimitiveArrayCounter__runsSuccessfully() {
    Map<String, int[]> counterMap = new HashMap<>();

    counterWithPrimitiveArray(counterMap);

    assertEquals(3, counterMap.get("China")[0]);
    assertEquals(2, counterMap.get("India")[0]);
}

private void counterWithPrimitiveArray(Map<String, int[]> counterMap) {
    for (String country : COUNTRY__NAMES) {
        counterMap.compute(country, (k, v) -> v == null ?
          new int[]{ 0 } : v)[0]++;
    }
}

値として

int array

を持つ単純な

HashMap

を作成したことに注意してください。


counterWithPrimitiveArray

メソッドでは、配列の各値を繰り返し処理しながら、


  • counterMap



    get

    を呼び出すには、国名を

キー
** キーがすでに存在していたかどうかを確認してください。エントリが

すでに存在する場合は、単一の「1」を持つプリミティブ整数配列の新しいインスタンスを作成します。エントリが存在しない場合は、配列内に存在するカウンタ値を増やします。

このメソッドはラッパー実装よりも優れています –

作成されるオブジェクトが少ないため


2.4.

Mut



MutableInteger


次に、次のようにプリミティブ整数カウンタを埋め込むラッパーオブジェクトを作成しましょう。

private static class MutableInteger {
    int count = 1;

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

   //getter and setter
}

上記のクラスをカウンターとして利用する方法を見てみましょう。

@Test
public void whenMapWithMutableIntegerCounter__runsSuccessfully() {
    Map<String, MutableInteger> counterMap = new HashMap<>();

    mapWithMutableInteger(counterMap);

    assertEquals(3, counterMap.get("China").getCount());
    assertEquals(2, counterMap.get("India").getCount());
}
private void counterWithMutableInteger(
  Map<String, MutableInteger> counterMap) {
    for (String country : COUNTRY__NAMES) {
        counterMap.compute(country, (k, v) -> v == null
          ? new MutableInteger(0) : v).increment();
    }
}


mapWithMutableInteger

メソッドでは、

COUNTRY

NAMES__配列内の各国を繰り返し処理しながら、

  • 国名をキーとして渡して

    counterMap

    の取得を呼び出します

  • キーがすでに存在するかどうかを確認してください。エントリが

存在しない場合は、カウンター値を1に設定する

MutableInteger

のインスタンスを作成します。国がマップに存在する場合は、

MutableInteger

に存在するカウンター値を増分します。

カウンタを作成するこの方法は、以前の方法よりも優れています – 同じ

MutableInteger

を再利用しているため、作成されるオブジェクトが少なくなるため** 。

これは、Apache Collections

HashMultiSet

が、内部的に

MutableInteger

として値を持つ

HashMap

を埋め込んでいる場所で機能する方法です。


3パフォーマンス分析

これが上記の各方法のパフォーマンスを比較したグラフです。

/uploads/CounterPerformance-3.jpg

上の図はJMHを使って作成されたもので、上記の統計を作成したコードは次のとおりです。

Map<String, Integer> counterMap = new HashMap<>();
Map<String, MutableInteger> counterMutableIntMap = new HashMap<>();
Map<String, int[]> counterWithIntArrayMap = new HashMap<>();
Map<String, Long> 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を使用して単語カウンタを作成するさまざまな方法について説明しました。

これらの例の実装はhttps://github.com/eugenp/tutorials/tree/master/core-java-8[GitHubプロジェクト]にあります – これはMavenベースのプロジェクトです。そのままインポートして実行します。