Javaでマップ値をインクリメントする最も効率的な方法


377

この質問がこのフォーラムにとってあまりにも基本的であるとは考えられないことを願っていますが、わかります。何度も実行されているパフォーマンスを向上させるために、コードをリファクタリングする方法を知りたいです。

Map(おそらくHashMap)を使用して単語頻度リストを作成しているとします。各キーは、カウントされている単語を含む文字列であり、値は、単語のトークンが見つかるたびに増加する整数です。

Perlでは、そのような値を増やすのは簡単です。

$map{$word}++;

しかし、Javaでは、はるかに複雑です。ここで私が現在やっている方法:

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

もちろん、どちらが新しいJavaバージョンのオートボクシング機能に依存しています。そのような値を増やすより効率的な方法を提案できるかどうか疑問に思います。コレクションフレームワークを避け、代わりに何か他のものを使用することには、パフォーマンス上の理由さえありますか?

更新:いくつかの回答のテストを行いました。下記参照。


java.util.Hashtableでも同じだと思います。
jrudolph 2008

2
もちろん、もし同じなら、Hashtableは実際にはMapなので。
ウイスキーシエラ

Java 8:computeIfAbsentの例:stackoverflow.com/a/37439971/1216775
akhil_mittal

回答:


367

いくつかのテスト結果

私はこの質問に対してたくさんの良い答えを得ました-おかげで-いくつかのテストを実行して、どの方法が実際に最も速いかを見つけることにしました。私がテストした5つの方法は次のとおりです。

  • 私が紹介した「ContainsKey」メソッド 質問で
  • Aleksandar Dimitrovによって提案された「TestForNull」メソッド
  • Hank Gayによって提案された "AtomicLong"メソッド
  • jrudolphが提案する "Trove"メソッド
  • phax.myopenid.comによって提案された「MutableInt」メソッド

方法

これが私がしたことです...

  1. 以下に示す違いを除いて同一の5つのクラスを作成しました。各クラスは、私が提示したシナリオに典型的な操作を実行する必要がありました。10MBのファイルを開いて読み取り、次にファイル内のすべての単語トークンの頻度カウントを実行しました。これは平均3秒しかかからなかったので、頻度カウント(I / Oではなく)を10回実行してもらいました。
  2. I / O操作ではなく、 10回の反復のループの時間を計り、JavaクックブックのIan Darwinのメソッドを基本的に使用して、かかった合計時間(クロック秒)を記録しました。
  3. 5つのテストすべてを連続して実行し、さらに3回テストしました。
  4. 各メソッドの4つの結果を平均しました。

結果

興味のある方のために、最初に結果と以下のコードを紹介します。

ContainsKeyの予想通り、私はその方法の速度と比較して、各メソッドのスピードをあげるための方法は、最も遅いでした。

  • ContainsKey: 30.654秒(ベースライン)
  • AtomicLong: 29.780秒(1.03倍の速度)
  • TestForNull: 28.804秒(1.06倍の速さ)
  • Trove: 26.313秒(1.16倍の速さ)
  • MutableInt: 25.747秒(1.19倍の速さ)

結論

MutableIntメソッドとTroveメソッドのみが10%以上のパフォーマンス向上を提供するという点で、はるかに高速であるように見えます。ただし、スレッド化が問題である場合、AtomicLongは他のスレッドよりも魅力的である可能性があります(本当にわかりません)。TestForNullも実行しましたfinal変数を使用が、違いはごくわずかでした。

さまざまなシナリオでのメモリ使用量をプロファイルしていないことに注意してください。MutableIntメソッドとTroveメソッドがメモリ使用量にどのように影響する可能性があるかについての洞察が得られた方からの連絡をお待ちしています。

個人的には、サードパーティのクラスをロードする必要がないため、MutableIntメソッドが最も魅力的だと思います。だから私がそれに問題を発見しない限り、それが私が行く可能性が最も高い方法です。

コード

以下は、各メソッドからの重要なコードです。

ContainsKey

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);

TestForNull

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
    freq.put(word, 1);
}
else {
    freq.put(word, count + 1);
}

AtomicLong

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map = 
    new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();

Trove

import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);

MutableInt

import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
  int value = 1; // note that we start at 1 since we're counting
  public void increment () { ++value;      }
  public int  get ()       { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
    freq.put(word, new MutableInt());
}
else {
    count.increment();
}

3
よくできました。マイナーなコメント-AtomicLongコードのputIfAbsent()呼び出しは、新しいAtomicLong(0)がすでにマップにある場合でもインスタンス化します。これを微調整してif(map.get(key)== null)を代わりに使用すると、おそらくこれらのテスト結果が改善されます。
リーコールドウェル

2
最近、MutableIntに似たアプローチで同じことをしました。それが最適なソリューションであったと聞いてうれしいです(テストを行わずに、それがそうだったと思いました)。
Kip

あなたが私より速いと聞いてうれしいよ、キップ。;-)そのアプローチの欠点を発見した場合はお知らせください。
グレゴリー

4
Atomic Longの場合、1つのステップでそれを行う方が効率的ではありません(つまり、2の代わりに1つの高価なgetオペレーションしかありません)。
smartnut007 '19

1
@gregory Java 8を検討しましたfreq.compute(word, (key, count) -> count == null ? 1 : count + 1)か?内部的にはcontainsKey、ハッシュされたルックアップが1より少ないため、ラムダのため、他と比較する方法を見るのは興味深いでしょう。
TWiStErRob

255

現在、Java 8でを使用する短い方法がありますMap::merge

myMap.merge(key, 1, Integer::sum)

それがすること:

  • 場合は、キーが存在していない、入れ1を値として
  • それ以外の場合は、1キーにリンクされた値に合計ます

詳細はこちら


常にjava 8が大好きです。これはアトミックですか?それとも同期で囲む必要がありますか?
Tiina

4
これは私にはうまくいかなかったようでしたが、 map.merge(key, 1, (a, b) -> a + b); うまく
いきまし

2
@Tiina原子性の特性は実装固有です。docs: "デフォルトの実装では、このメソッドの同期またはアトミック性のプロパティは保証されません。アトミック性の保証を提供する実装では、このメソッドをオーバーライドし、その同時実行性のプロパティを文書化する必要があります。特に、サブインターフェースConcurrentMapのすべての実装では、関数が一度適用されるかどうかを文書化する必要があります値が存在しない場合にのみアトミックに。」
jensgram 2018

2
groovyの場合Integer::sum、BiFunctionとしては受け入れられず、@ russterが記述された方法で応答するのを嫌いました。これは私にとってはMap.merge(key, 1, { a, b -> a + b})
うまくいっ

2
@russter、私はあなたのコメントが1年以上前だったことを知っていますが、なぜそれがあなたにとってうまくいかなかったのを覚えていますか?コンパイルエラーが発生しましたか、値が増加しませんでしたか?
ポール・

44

2016年の調査:https : //github.com/leventov/java-word-countベンチマークソースコード

メソッドごとの最良の結果(小さいほど良い):

                 time, ms
kolobokeCompile  18.8
koloboke         19.8
trove            20.8
fastutil         22.7
mutableInt       24.3
atomicInteger    25.3
eclipse          26.9
hashMap          28.0
hppc             33.6
hppcRt           36.5

時間\空間結果:


2
ありがとう、これは本当に役に立ちました。Guavaのマルチセット(たとえば、HashMultiset)をベンチマークに追加すると便利です。
cabad 2015年

34

Google グアバはあなたの友達です...

...少なくとも場合によっては。彼らはこの素晴らしいAtomicLongMapを持っています。あなたは長い間扱っているので特にいいマップの価値をできるだけ。

例えば

AtomicLongMap<String> map = AtomicLongMap.create();
[...]
map.getAndIncrement(word);

値に1以上を追加することもできます。

map.getAndAdd(word, 112L); 

7
AtomicLongMap#getAndAddlongラッパークラスではなく、プリミティブを取ります。する意味がありませんnew Long()。そしてAtomicLongMap、パラメータ化されたタイプです。として宣言する必要がありますAtomicLongMap<String>
Helder Pereira

32

@ハンクゲイ

私の(あまり役に立たない)コメントへのフォローアップとして:Troveは進むべき道のように見えます。何らかの理由で、あなたは標準のJDKに固執したい、場合は、ConcurrentMapがAtomicLongをコードすることができます小さなビットよりよい、YMMVかかわらず。

    final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.putIfAbsent("foo", new AtomicLong(0));
    map.get("foo").incrementAndGet();

1はのマップの値のままになりますfoo。現実的には、このアプローチが推奨するのは、スレッディングに対する親しみやすさの向上だけです。


9
putIfAbsent()は値を返します。戻り値をローカル変数に格納して、getを再度呼び出すのではなく、それをincrementAndGet()に使用することは大きな改善になる可能性があります。
smartnut007 2011

指定されたキーがMap内の値に関連付けられていない場合、putIfAbsentはnull値を返す可能性があるため、返された値を使用するように注意します。docs.oracle.com/javase/8/docs/api/java/util/...
bumbur

27
Map<String, Integer> map = new HashMap<>();
String key = "a random key";
int count = map.getOrDefault(key, 0); // ensure count will be one of 0,1,2,3,...
map.put(key, count + 1);

そして、それが単純なコードで値をインクリメントする方法です。

メリット:

  • 新しいクラスを追加したり、可変intの別の概念を使用したりする必要はありません
  • ライブラリに依存しない
  • 正確に何が起こっているのかを理解しやすい(あまり抽象化されていない)

欠点:

  • ハッシュマップはget()とput()で2回検索されます。したがって、これは最もパフォーマンスの高いコードではありません。

理論的には、get()を呼び出すと、put()の場所がわかっているため、再度検索する必要はありません。ただし、ハッシュマップでの検索には通常、ごくわずかな時間でこのパフォーマンスの問題を無視できます。

しかし、問題に真剣に取り組んでいる場合、完全主義者である場合、もう1つの方法はマージ方法を使用することです。これは、(おそらく)1回だけマップを検索するため、前のコードスニペットよりも(おそらく)より効率的です。このコードは一目では明らかではなく、短くてパフォーマンスが高いです)

map.merge(key, 1, (a,b) -> a+b);

提案:ほとんどの場合、コードの可読性はほとんどパフォーマンスの向上よりも重要です。最初のコードスニペットが理解しやすい場合は、それを使用してください。しかし、あなたが2番目の問題をうまく理解できるなら、あなたはそれのために行くこともできます!


getOfDefaultメソッドはJAVA 7では使用できません。JAVA7でこれを実現するにはどうすればよいですか?
tanvi 2016年

1
その場合、他の回答に頼る必要があるかもしれません。これが唯一のJava 8で動作します
off99555

1
マージソリューションの+1。これは、ハッシュコードの計算に1回支払うだけでよいので(使用しているマップが適切にメソッドをサポートしている場合)、代金を支払う可能性がないので、これが最もパフォーマンスの高い機能になります3回
Ferrybig 2017

2
メソッド推論の使用:map.merge(key、1、Integer :: sum)
earandap

25

この種のことについては、常にGoogleコレクションライブラリを確認することをお勧めします。この場合、Multisetがうまくいきます:

Multiset bag = Multisets.newHashMultiset();
String word = "foo";
bag.add(word);
bag.add(word);
System.out.println(bag.count(word)); // Prints 2

キーやエントリなどを反復処理するためのマップのようなメソッドがあります。内部では現在、実装でを使用しているHashMap<E, AtomicInteger>ため、ボックス化コストは発生しません。


上記の回答は、トーバー応答を反映する必要があります。APIはその投稿(3年前:)から変更されました
Steve

count()マルチセットのメソッドはO(1)またはO(n)時間(最悪の場合)で実行されますか?この点についてのドキュメントは不明確です。
アダムパーキン2013年

この種のもののための私のアルゴリズム:if(hasApacheLib(thing))はapacheLibを返します。else if(hasOnGuava(thing))はグアバを返します。通常、私はこれらの2つのステップを通過しません。:)
digao_mb 2015年

22

最初の試みが

int count = map.containsKey(word)?map.get(word):0;

マップに2つの潜在的にコストのかかる操作、つまりcontainsKeyおよびが含まれていますget。前者は後者と非常によく似た操作を実行する可能性があるため、同じ作業を2回実行しています。

Map for APIを見ると、get通常null、マップに要求された要素が含まれていない場合に操作が返されます。

これは次のような解決策になることに注意してください

map.put(key、map.get(key)+ 1);

NullPointerExceptionsを生成する可能性があるため、危険です。null最初に確認する必要があります。

も注意してください。これは非常に重要であり、HashMaps 当然のことながら含むことができますnulls。したがって、返されたすべてnullが「そのような要素はありません」と言うわけではありません。この点で、containsKey振る舞いは異なるからget、実際にあなたを伝えるにするかどうか、そのような要素があります。詳細については、APIを参照してください。

ただし、場合によっては、stored nullと「noSuchElement」を区別したくない場合があります。を許可したくない場合はnullHashtable。アプリケーションの複雑さによっては、他の回答ですでに提案されているようにラッパーライブラリを使用する方が、手動で処理するための優れたソリューションになる場合があります。

答えを完成させるには(そして、編集機能のおかげで、最初にそれを入れるのを忘れていました!)、それをネイティブに行う最善の方法はgetfinal変数に入れてチェックしnullputそれをで戻すこと1です。変数は、finalとにかく不変だからです。コンパイラはこのヒントを必要としないかもしれませんが、その方がより明確です。

最終的なHashMapマップ= generateRandomHashMap();
最終オブジェクトキー= fetchSomeKey();
最終的な整数i = map.get(key);
if(i!= null){
    map.put(i + 1);
} そうしないと {
    //何かをする
}

オートボクシングに依存したくない場合は、map.put(new Integer(1 + i.getValue()));代わりに次のように言う必要があります。


groovyの初期のマップされていない/ null値の問題を回避するために、私は最終的に次のようにします:counts.put(key、(counts.get(key)?:0)+ 1)//過度に複雑なバージョンの++
Joe Atzberger

2
または、最も単純に:counts = [:]。withDefault {0} // ++ away
Joe Atzberger

18

もう1つの方法は、可変整数を作成することです。

class MutableInt {
  int value = 0;
  public void inc () { ++value; }
  public int get () { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt> ();
MutableInt value = map.get (key);
if (value == null) {
  value = new MutableInt ();
  map.put (key, value);
} else {
  value.inc ();
}

もちろん、これは追加のオブジェクトを作成することを意味しますが、Integer(Integer.valueOfを使用した場合でも)を作成する場合と比べて、オーバーヘッドはそれほど多くありません。


5
MutableIntを最初にマップに配置するときに、1から開始したくないですか?
トム・ホーティン-タックライン2008

5
Apacheのcommons-langには、MutableIntがすでに記述されています。
SingleShot

11

Java 8で提供されるインターフェースでcomputeIfAbsentメソッドを利用できMapます。

final Map<String,AtomicLong> map = new ConcurrentHashMap<>();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("B", k->new AtomicLong(0)).incrementAndGet();
map.computeIfAbsent("A", k->new AtomicLong(0)).incrementAndGet(); //[A=2, B=1]

このメソッドcomputeIfAbsentは、指定されたキーがすでに値に関連付けられているかどうかをチェックしますか?関連付けられた値がない場合、指定されたマッピング関数を使用してその値を計算しようとします。いずれの場合も、指定されたキーに関連付けられている現在の(既存または計算された)値を返します。計算された値がnullの場合はnullを返します。

余談ですが、複数のスレッドが共通の合計を更新する状況がある場合は、LongAdderクラスを確認できます。競合が多い場合AtomicLong、このクラスの予想されるスループットは、よりも大幅に高くなりますが、スペースの消費量が多くなります。


なぜconcurrentHashmapとAtomicLongなのですか?
エレオン

7

128以上のintのすべてのボクシングはオブジェクトの割り当てを引き起こすため、メモリのローテーションが問題になる可能性があります(Integer.valueOf(int)を参照)。ガベージコレクターは有効期間が短いオブジェクトを非常に効率的に処理しますが、パフォーマンスはある程度低下します。

作成される増分の数がキーの数(この場合はワード)を大幅に上回ることがわかっている場合は、代わりにintホルダーの使用を検討してください。Phaxはすでにこのためのコードを提示しています。ここでも、2つの変更が加えられています(ホルダークラスが静的になり、初期値が1に設定されています)。

static class MutableInt {
  int value = 1;
  void inc() { ++value; }
  int get() { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt>();
MutableInt value = map.get(key);
if (value == null) {
  value = new MutableInt();
  map.put(key, value);
} else {
  value.inc();
}

極端なパフォーマンスが必要な場合は、プリミティブな値の型に直接合わせたMap実装を探してください。jrudolphがGNU Troveについて言及しました。

ちなみに、この主題に適した検索用語は「ヒストグラム」です。


5

containsKey()を呼び出す代わりに、map.getを呼び出して、戻り値がnullかどうかを確認する方が高速です。

    Integer count = map.get(word);
    if(count == null){
        count = 0;
    }
    map.put(word, count + 1);

3

これがボトルネックになっていますか?パフォーマンス分析を行いましたか?

NetBeansプロファイラー(無料でNB 6.1に組み込まれています)を使用してホットスポットを確認してください。

最後に、JVMアップグレード(たとえば1.5-> 1.6から)は、多くの場合、安価なパフォーマンスブースターです。ビルド番号をアップグレードしても、パフォーマンスが向上します。Windowsで実行していて、これがサーバークラスのアプリケーションである場合は、コマンドラインで-serverを使用してサーバーホットスポットJVMを使用します。LinuxおよびSolarisマシンでは、これは自動検出されます。


3

いくつかのアプローチがあります:

  1. Googleコレクションに含まれているセットのようなバッグアルゴリズムを使用します。

  2. マップで使用できる可変コンテナを作成します。


    class My{
        String word;
        int count;
    }

そしてput( "word"、new My( "Word"));を使用します。次に、それが存在するかどうかを確認し、追加時に増分できます。

リストを使用して独自のソリューションをローリングすることは避けてください。内部ループの検索と並べ替えを行うと、パフォーマンスが低下します。最初のHashMapソリューションは実際にはかなり高速ですが、Googleコレクションにあるような適切なソリューションがおそらくより優れています。

Googleコレクションを使用して単語を数えると、次のようになります。



    HashMultiset s = new HashMultiset();
    s.add("word");
    s.add("word");
    System.out.println(""+s.count("word") );


HashMultisetの使用は非常に重要です。バッグアルゴリズムは、単語を数えるときに必要なものだからです。


3

あなたの解決策は標準的な方法だと思いますが、あなたが気づいたように、それはおそらく最速の方法ではありません。

あなたはGNU Troveを見るかもしれません。これは、あらゆる種類の高速プリミティブコレクションを含むライブラリです。この例では、望みどおりの処理を行うメソッドadjustOrPutValueを持つTObjectIntHashMapを使用します。


TObjectIntHashMapへのリンクが壊れています。これは正しいリンクです:trove4j.sourceforge.net/javadocs/gnu/trove/map/...
Erelシーガル-Halevi

おかげで、エレル、私はリンクを修正しました。
jrudolph 2011

3

MutableIntアプローチのバリエーションは、少しハックすればさらに高速になる可能性があり、単一要素のint配列を使用します。

Map<String,int[]> map = new HashMap<String,int[]>();
...
int[] value = map.get(key);
if (value == null) 
  map.put(key, new int[]{1} );
else
  ++value[0];

このバリエーションを使用してパフォーマンステストを再実行できれば興味深いでしょう。最速かもしれません。


編集:上記のパターンは私にとってはうまくいきましたが、結局私はTroveのコレクションを使用して、作成していたいくつかの非常に大きなマップのメモリサイズを減らすように変更しました-ボーナスとして、それもより高速でした。

本当に素晴らしい機能の1つは、TObjectIntHashMapクラスに単一のadjustOrPutValue呼び出しがあり、そのキーに既に値があるかどうかに応じて、初期値を設定するか、既存の値をインクリメントすることです。これは増分に最適です。

TObjectIntHashMap<String> map = new TObjectIntHashMap<String>();
...
map.adjustOrPutValue(key, 1, 1);

3

GoogleコレクションHashMultiset:
-非常にエレガントに使用できます
が、CPUとメモリを消費します

最善の方法は次のような方法です。Entry<K,V> getOrPut(K); (エレガントで低コスト)

このようなメソッドは、ハッシュとインデックスを1回だけ計算します。その後、エントリで必要な処理を実行できます(値を置換または更新します)。

よりエレガント:
-取るHashSet<Entry>
-それを拡張してget(K)、必要に応じて新しいエントリを配置する
-エントリを独自のオブジェクトにすることができます。
->(new MyHashSet()).get(k).increment();


3

非常に単純です。組み込み関数を次のMap.javaように使用するだけです

map.put(key, map.getOrDefault(key, 0) + 1);

これは値を増分せず、現在の値またはキーに値が割り当てられていない場合は0を設定するだけです。
siegi

値は++... 増分できます。OMGはとても簡単です。@siegi
sudoz

レコードの場合:++変数はそのオペランドとして必要ですが、値しかないため、この式のどこでも機能しません。あなたの+ 1作品の追加も。これで、ソリューションはoff99555の回答と同じになります
siegi

2

「put」には「get」が必要です(キーが重複しないようにするため)。
したがって、直接「put」を実行し
、以前の値があった場合は、追加を実行します。

Map map = new HashMap ();

MutableInt newValue = new MutableInt (1); // default = inc
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
  newValue.add(oldValue); // old + inc
}

カウントが0から始まる場合は、1を追加します(またはその他の値...)

Map map = new HashMap ();

MutableInt newValue = new MutableInt (0); // default
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
  newValue.setValue(oldValue + 1); // old + inc
}

注意:このコードはスレッドセーフではありません。同時に更新するのではなく、マップを構築して使用するために使用します。

最適化:ループでは、古い値を保持して次のループの新しい値にします。

Map map = new HashMap ();
final int defaut = 0;
final int inc = 1;

MutableInt oldValue = new MutableInt (default);
while(true) {
  MutableInt newValue = oldValue;

  oldValue = map.put (key, newValue); // insert or...
  if (oldValue != null) {
    newValue.setValue(oldValue + inc); // ...update

    oldValue.setValue(default); // reuse
  } else
    oldValue = new MutableInt (default); // renew
  }
}

1

たとえば、さまざまなプリミティブラッパーIntegerは不変であるため、AtomicLongのようなもので実行できない限り、要求していることを行うためのより簡潔な方法はありません。私はそれをすぐに試して更新することができます。ところで、Hashtable Collections Frameworkの一部です。


1

(値を0に初期化するために)Apacheコレクションレイジーマップを使用し、そのマップの値としてApache LangのMutableIntegersを使用します。

最大のコストは、メソッドでマップを2回検索することです。私の場合は、一度だけ実行する必要があります。値を取得し(存在しない場合は初期化されます)、増分します。


1

機能のJavaライブラリのTreeMapデータ構造がありupdate、最新のトランクの頭の中でメソッドを:

public TreeMap<K, V> update(final K k, final F<V, V> f)

使用例:

import static fj.data.TreeMap.empty;
import static fj.function.Integers.add;
import static fj.pre.Ord.stringOrd;
import fj.data.TreeMap;

public class TreeMap_Update
  {public static void main(String[] a)
    {TreeMap<String, Integer> map = empty(stringOrd);
     map = map.set("foo", 1);
     map = map.update("foo", add.f(1));
     System.out.println(map.get("foo").some());}}

このプログラムは「2」を出力します。


1

@Vilmantas Baranauskas:この回答に関して、私が担当者ポイントがあればコメントしますが、しません。ここで定義されたCounterクラスは、value()を同期せずにinc()を同期するだけでは不十分であるため、スレッドセーフではないことに注意したいと思います。value()を呼び出す他のスレッドは、更新と発生前の関係が確立されていない限り、値を確認できるとは限りません。


誰かの回答を参照したい場合は、上部に@ [Username]を使用します(例:@Vilmantas Baranauskas <コンテンツはここに移動します>
Hank Gay

私はそれをきれいにするためにその修正を行いました。
Alex Miller

1

どれほど効率的かわかりませんが、以下のコードも機能しますBiFunction。最初にを定義する必要があります。さらに、この方法でインクリメントするだけではありません。

public static Map<String, Integer> strInt = new HashMap<String, Integer>();

public static void main(String[] args) {
    BiFunction<Integer, Integer, Integer> bi = (x,y) -> {
        if(x == null)
            return y;
        return x+y;
    };
    strInt.put("abc", 0);


    strInt.merge("abc", 1, bi);
    strInt.merge("abc", 1, bi);
    strInt.merge("abc", 1, bi);
    strInt.merge("abcd", 1, bi);

    System.out.println(strInt.get("abc"));
    System.out.println(strInt.get("abcd"));
}

出力は

3
1

1

あなたが使用している場合はEclipseのコレクションを、あなたが使用することができますHashBag。これはメモリ使用量の点で最も効率的なアプローチであり、実行速度の点でも優れたパフォーマンスを発揮します。

HashBagオブジェクトのMutableObjectIntMap代わりにプリミティブintを格納するaに支えられていCounterます。これにより、メモリのオーバーヘッドが減少し、実行速度が向上します。

HashBag 必要なAPIを提供します。 Collectionアイテムの出現回数をクエリできる。

以下はEclipse Collections Kataの例です。

MutableBag<String> bag =
  HashBag.newBagWith("one", "two", "two", "three", "three", "three");

Assert.assertEquals(3, bag.occurrencesOf("three"));

bag.add("one");
Assert.assertEquals(2, bag.occurrencesOf("one"));

bag.addOccurrences("one", 4);
Assert.assertEquals(6, bag.occurrencesOf("one"));

注:私はEclipseコレクションのコミッターです。


1

Java 8 Map :: compute()を使用することをお勧めします。キーが存在しない場合も考慮します。

Map.compute(num, (k, v) -> (v == null) ? 1 : v + 1);

mymap.merge(key, 1, Integer::sum)
デット

-2

多くの人がJavaトピックでGroovyの回答を検索するため、GroovyでJavaを実行する方法は次のとおりです。

dev map = new HashMap<String, Integer>()
map.put("key1", 3)

map.merge("key1", 1) {a, b -> a + b}
map.merge("key2", 1) {a, b -> a + b}

-2

Java 8のシンプルで簡単な方法は次のとおりです。

final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.computeIfAbsent("foo", key -> new AtomicLong(0)).incrementAndGet();

-3

私はあなたの質問を正しく理解しているといいのですが、私はPythonからJavaに来て、あなたの闘争に共感できるようにします。

あなたが持っている場合

map.put(key, 1)

あなたがするだろう

map.put(key, map.get(key) + 1)

お役に立てれば!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.