web-dev-qa-db-ja.com

マップを高速より小さくする必要がある場合、どのMap <K、V>の実装を使用すればよいですか?

私は自分のプログラムでHashMapをいつも使用しています。これは、通常(最も適切に使用された場合)が最も効率的であり、大きなマップに簡単に対応できるためです。列挙キーに非常に役立つEnumMapについて知っていますが、多くの場合、非常に大きくなることはなく、すぐに破棄される可能性があり、同時実行性の問題がない小さなマップを生成しています。

HashMap<K,V>これらの小規模なローカルおよび一時的な用途には複雑すぎますか?これらの場合に使用できる別の単純な実装はありますか?

MapArrayListに似たList実装を探していると思います。存在しますか?


応答の後で追加:

これは遅いが非常にシンプルな実装のシナリオです かもしれない 良くなる-私がたくさんいるとき、これらのMapsの多く。たとえば、これらの小さな地図が100万ほどあり、それぞれに少数(多くの場合3つ未満)のエントリがあるとします。参照率が低い-ほとんどの場合、破棄される前に実際に参照していない可能性があります。それでもHashMapが彼らにとって最良の選択であるということですか?

リソースの使用率は、単なる速度以上のものです。たとえば、ヒープをあまり断片化せず、GCに時間がかかるようなものを望みます。

HashMapが正しい答えかもしれませんが、これは時期尚早の最適化のケースではありません(または少なくともそうではないかもしれません)。


少し考えてから追加しました:

私は自分のSmallMapを手動でコーディングすることにしました。 AbstractMapで簡単に作成できます。また、SmallMapを既存のMapから構築できるように、いくつかのコンストラクターを追加しました。

その過程で、Entrysを表す方法と、SmallSetメソッドにentrySetを実装する方法を決定する必要がありました。

私はコーディング(およびこれの単体テスト)によって多くのことを学び、他の誰かが望む場合に備えて、これを共有したいと思います。 github here にあります。

30
Steve Powell

JavaにはMapの標準的な小さな実装はありません。 HashMapは、最高で最も柔軟なMap実装の1つであり、他に勝るものはありません。ただし、非常に小さな要件領域(ヒープの使用と構築の速度が最も重要な領域)では、より優れた処理が可能です。

これがどのように行われるかを示すために、GitHubに SmallCollections を実装しました。私は成功したかどうかについてloveいくつかコメントします。私が持っていることは決して確かではありません。

ここで提供された回答は役立つ場合もありますが、一般に、ポイントを誤解する傾向があります。いずれにせよ、自分の質問に答えることは、結局のところ、与えられた質問よりもはるかに役に立ちました。

ここでの質問はその目的を果たしており、それが私が「自分で答えた」理由です。

19
Steve Powell

これは時期尚早の最適化だと思います。メモリに問題がありますか?作成するマップが多すぎることによるパフォーマンスの問題?そうでない場合は、HashMapで問題ないと思います。

さらに、APIを見ると、HashMapよりも単純なものは何もありません。

問題が発生している場合は、内部が非常に単純な独自のMap実装をロールできます。しかし、私はあなたがデフォルトのMap実装よりもうまくやるとは疑い、さらに新しいクラスが機能することを確認するオーバーヘッドがあります。この場合、デザインに問題がある可能性があります。

12
hvgotcodes

HashMapは、おそらく最も軽量でシンプルなコレクションです。

時々、より効率的な解決策はPOJOを使用することです。例えばキーがフィールド名であるか、値がプリミティブである場合。

4
Peter Lawrey

HashMapは、平均的なケースO(1) putsおよびgetsを提供するので、良い選択です。 SortedMapの実装(つまり、TreeMap O(log n) putsおよびgets)のように順序付けを保証するものではありませんが、順序付けの要件がない場合は、HashMapの方が適しています。

2
Dev

@hvgotcodesに同意しますが、これは時期尚早の最適化ですが、ツールボックス内のすべてのツールを知ることは依然として良いことです。

マップの内容に対して多くの反復を行う場合、LinkedHashMapは通常、HashMapよりもかなり高速です。同時にMapを操作する多くのスレッドがある場合は、多くの場合、ConcurrentHashMapの方が適しています。小さなデータセットに対してMapの実装が非効率的であることを心配する必要はありません。通常、これは逆です。不正なハッシュ値がある場合、または何かが原因でバケットのバケット数が少なすぎる場合、誤って作成されたマップは大量のデータで簡単に非効率になります。

もちろん、HashMapがまったく意味をなさない場合もあります。たとえば、キー0、1、2で常にインデックス付けする3つの値がある場合ですが、私はあなたがそれを理解していると思います:-)

1
Fredrik

Androidには、メモリを最小限に抑えることを目的とした ArrayMap があります。コアに加えて、v4サポートライブラリにもあります。理論的には、OracleまたはOpenJDK JRE用にコンパイルできるはずです。以下は githubのv4サポートライブラリのフォークにあるArrayMapのソース へのリンクです。

1
Travis Well

HashMapは、初期化の方法に応じて(作成時に)メモリの使用量を増減します。バケットが多いほどメモリ使用量が多くなりますが、アクセスは高速になります大量のアイテムの場合;必要なアイテムの数が少ない場合は、小さな値で初期化できます。これにより、バケットが少なくなり、高速になります(それぞれがいくつかのアイテムを受け取るため)。正しく設定すれば、メモリの浪費はありません(トレードオフは基本的にメモリ使用量と速度です)。

ヒープの断片化とGCサイクルの浪費などに関しては、Map実装ができることはそれほど多くありません。それはすべて、設定した方法にフォールバックします。これはJavaの実装ではないが、一般的な(たとえば、EnumMapが行うようなキー値について何も想定できない)ハッシュテーブル(HashTablesではない)が最善であるという事実を理解するマップ構造の実装。

1
Viruzzo

IdentityHashMap

パフォーマンスに本当に関心がある場合は、 IdentityHashMap を検討してください。

このクラスは、キーの内容ではなく、メモリ内の参照によってキーを追跡します。これにより、キーのhashCodeメソッドを呼び出す必要がなくなります。これは、クラスの内部にキャッシュされていない場合、長くなる場合と長くならない場合があります。

このクラスは、基本操作(getおよびput)に対して一定時間のパフォーマンスを提供します。

Map のこの実装を選択するときは、Javadocを注意深く読んでください。

Map.of

Java 9と JEP 269:コレクションの便利なファクトリメソッド は、Map.ofでマップを簡潔に定義するためのリテラル構文の便利さをもたらしました(およびList.ofでリストします)。

変更不可能なマップを使用できる状況の場合は、この単純な構文を使用してください。

Map.ofメソッドはMapインターフェースオブジェクトを返すことに注意してください。基礎となる具体的な Map クラスは不明であり、特定のコードやJavaのバージョンによって異なる場合があります。 Java実装は具象クラスの選択で自由に最適化できます。たとえば、Map.ofがキーに列挙型を使用する場合、 Map.of コマンドは- EnumMap または、非常に小さなマップ用に最適化された未開発のクラスを選択する場合があります。

Map< DayOfWeek , Employee > dailyWorker = 
    Map.of(
        DayOfWeek.MONDAY , alice ,
        DayOfWeek.TUESDAY , bob ,
        DayOfWeek.WEDNESDAY , bob ,
        DayOfWeek.THURSDAY , alice ,
        DayOfWeek.FRIDAY , carol ,
        DayOfWeek.SATURDAY , carol ,
        DayOfWeek.SUNDAY , carol
    )
;

時期尚早の最適化

他の人が指摘したように、あなたはおそらく時期尚早の最適化の罠に陥っています。アプリのパフォーマンスは、選択した小さなマップの実装によって大きな影響を受けることはほとんどありません。

その他の考慮事項

並行性、NULLの許容度、反復順序など、他の考慮すべき点があります。

これは、Java 11.にバンドルされているMapの10の実装のこれらの側面をリストしたグラフです。

Table of map implementations in Java 11, comparing their features

0
Basil Bourque

AirConcurrentMapと呼ばれる代替手段があります。これは、私が見つけた他のどのマップよりも1Kエントリ以上のメモリ効率が高く、キーベースの操作のConcurrentSkipListMapよりも高速で、反復のどのマップよりも高速で、並列スキャン用の内部スレッドプールを備えています。これは順序付けされたNavigableMapとConcurrentMapです。非営利目的でソースを使用しない場合は無料で、ソースの有無にかかわらず商用ライセンスが付与されます。グラフについては、boilerbay.comを参照してください。完全な開示:私は著者です。

AirConcurrentMapは標準に準拠しているため、通常のマップであってもどこでもプラグ互換です。

イテレータはすでに非常に高速で、特に1Kエントリを超えています。より高速のスキャンは、「visitor」モデルを使用して、1つのvisit(k、v)コールバックでJava 8パラレルストリームの速度に達します。AirConcurrentMapパラレルスキャンはJava約4倍の8つの並列ストリーム。スレッド化されたビジターは、map/reduceのいずれかを思い出させるsplit()およびmerge()メソッドをシングルスレッドビジターに追加します。

static class ThreadedSummingVisitor<K> extends ThreadedMapVisitor<K, Long> {
    private long sum;
    // This is idiomatic
    long getSum(VisitableMap<K, Long> map) {
        sum = 0;
        map.getVisitable().visit(this);
        return sum;
    }

    @Override
    public void visit(Object k, Long v) {
        sum += ((Long)v).longValue();
    }

    @Override
    public ThreadedMapVisitor<K, Long> split() {
        return new ThreadedSummingVisitor<K>();
    }

    @Override
    public void merge(ThreadedMapVisitor<K, Long> visitor) {
        sum += ((ThreadedSummingVisitor<K>)visitor).sum;
    }
}
...
// The threaded summer can be re-used in one line now.
long sum = new ThreadedSummingVisitor().getSum((VisitableMap)map);
0
Roger Deran

私も興味があり、実験のために、フィールドにキーと値を格納し、最大5つのエントリを許可するマップを作成しました。 4少ないメモリを消費し、HashMapよりも16倍速く動作します https://github.com/stokito/jsmallmap

0