blog

Mapの値をインクリメントする最も効率的な方法:キーを一度だけ検索する!

この問題は最初は基本的なことに思えるかもしれませんが、フォーラムでは頻繁に議論されています。この投稿では、Map内で一度だけキーを検索する方法について説明します。...

Nov 13, 2015 · 4 min. read
シェア

この質問は最初は基本的なことに思えるかもしれませんが、フォーラムでは頻繁に議論されています。この投稿では、マップの中で一度だけキーを検索する方法について説明します。

例を見てみましょう。Mapを使って単語の頻度表を作成し、各キーはカウントされる単語で、値はその頻度です。簡単な実装は次のようになります:

int count = map.containsKey(string) ? map.get(string) : 0;  
map.put(string, count + 1); 

このコードには時間を浪費する可能性のある操作が3つ含まれているため、あまり効率的ではありません。統計処理を実行するたびに、Map内のキーを検索します。では、これを例にして、Mapに値を追加することでパフォーマンスがどのように向上するかを見てみましょう。

Integer vs MutableInteger vs AtomicInteger

パフォーマンスを消費する操作を3回呼び出さなければならない大きな理由は、カウントにIntegerを使用していることです。Javaでは、Integerは変更できません。一度構築された整数値を変更することはできません。そのため、カウンターを大きくするには、マップから整数を取得し、別の新しい整数を作成し、それを追加してマップに戻す必要があります。

カウンターを変更可能にするには、いくつかの方法があります。そのうちの1つは、以下に示すような独自のMutableIntegerを作成することです:

public class MutableInteger {  
 
  private int val;  
 
  public MutableInteger(int val) {  
    this.val = val;  
  }  
 
  public int get() {  
    return val;  
  }  
 
  public void set(int val) {  
    this.val = val;  
  }  
} 

別のアプローチとしては、JavaのAtomicIntegerを使用することが考えられます。これは、アトミックな成長カウンターを必要とするアプリケーションなどで使用されます。AtomicIntegerを***として使用する理由は、整数を操作する際にスレッドセーフでありたいからです。そのため、Integerの代わりには使えません。このため、プロジェクトでスレッドセーフを重要視しないのであれば、AtomicIntegerはお勧めしません。

検索キーは一度だけ

MutableIntegerを使用した後、上記のコードを以下のように変更します:

if (map.containsKey(string)) {  
  MutableInteger count = map.get(string);  
  count.set(count.get() + 1);  
} else {  
  map.put(string, new MutableInteger(1));  
}  

または

MutableInteger count = map.get(string);  
if (count != null) {  
  count.set(count.get() + 1);  
} else {  
  map.put(string, new MutableInteger(1));  
}  

キーがまだ存在しない最悪の場合、このコードは2つの検索を実行します:1つはMutableIntegerを取得し、もう1つは値を設定します。これは前のコードよりも最適化されています。しかし、今のところ、[Map.putt()]メソッドをチェックアウトするだけではいけません)。 メソッドをチェックしてみてください。このメソッドは以前に関連付けられたキーの値を返すことがわかります。つまり、reacquireオブジェクトとsetメソッドをマージすることが可能です。しかし、おそらくあなたは、最初にカウンターを取得することなく、新しいカウンターを設定する方法を疑問に思っているのではないでしょうか?これでようやく、この記事で最も厄介な部分にぶち当たったことになります:単純にゼロ頻度カウンターを使えばいいのです!

public int incrementCount(K key, int count) {  
    MutableInteger tmpCount = new MutableInteger(0);  
    MutableInteger oldCount = map.put(key, tmpCount);  
    if (oldCount != null) {  
      count += oldCount.get();  
    }  
    tmpCount.set(count);  
    return count;  
  }  

必要な操作をすべてクラスに入れておくと、将来使うときにとても便利そうです。そこで、Counterクラスを作成し、一般公開することにしました。このCounterでは、コレクションが定義され、オブジェクトがコレクションに現れる回数を記録します。a,a,b,c}というコレクションを含むCounterがあるとします。getCount() メソッドを呼び出すと "a" に対して 2 が返されますが、keySet() を呼び出すと {a,b,c} が返されます。このクラスは Map のように動作しますが、Map よりもシンプルなメソッドを持っています。

Counter のコンストラクタと addAll() メソッドを使用して、別のカウンタの内容をコピーできます。Counter クラスは、IntCounter と AbstractMapBag で変更できます。

カウンターの主な操作方法は以下の通り:

  • と :与えられたキー値に基づいて、現在のカウントから与えられた値をインクリメント/減算します。もしそのキー値が以前に出現したことがなければ、カウントは0であると判断できます。subtractメソッドは、その値を-1に設定します。
  • 指定されたキーの現在のカウントを返します。
  • and:指定されたキー値のカウントを返します。カウントは、指定されたしきい値と等しいか、それより大きいか、またはそれ以下でなければなりません。この集合の要素は0かもしれませんが、空にはなりません。
  • and :このカウンタで最小または***カウントのキーの値を検索して返します。最小または***カウントが複数ある場合は、ランダムな値が返されます。Counter が空の場合は null を返します。
Read next

新しいWindows Server 2012 R2 SMB PowerShellコマンドを使用する

4.0は、Windows 8.1およびWindows Server 2012 R2の多くの管理機能向上の1つです。この記事では、SMB 3.02の新機能に関連する、コマンドレットにおけるSMB関連の改善について記載します。

Nov 9, 2015 · 3 min read