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
と
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ベースのプロジェクトです。そのままインポートして実行します。