web-dev-qa-db-ja.com

リストのインデックス作成中の最高のHashMap初期容量

リスト(_List<T> list_)があり、マップ(_HashMap<Integer, T> map_)を使用して、IDでオブジェクトのインデックスを作成します。以下のコードのように、HashMapコンストラクターでは常にlist.size()初期容量として使用します。これは、この場合に使用するのに最適な初期容量ですか?

:マップにアイテムを追加することはありません。

_List<T> list = myList;
Map<Integer, T> map = new HashMap<Integer, T>(list.size());
for(T item : list) {
    map.put(item.getId(), item);
}
_
26
Italo Borssatto

HashMapの再ハッシュを避け、他の要素がHashMapに配置されないことがわかっている場合は、負荷係数と初期容量を考慮する必要があります。負荷係数 HashMapのデフォルトは0.75

新しいエントリが追加されるたびに、リハッシュが必要かどうかを判断する計算が行われます。 putは新しいキー/値を配置します。したがって、list.size()の初期容量と1の負荷係数を指定した場合、最後のputの後に再ハッシュされます。したがって、再ハッシュを防ぐには、負荷係数1と容量list.size() + 1を使用します。

[〜#〜]編集[〜#〜]

HashMapソースコードを見ると、oldサイズがしきい値以上の場合は再ハッシュされるため、最後のput。したがって、list.size()の容量は問題ないようです。

HashMap<Integer, T> map = new HashMap<Integer, T>(list.size(), 1.0);

以下は、関連するHashMapソースコードです。

void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}
27
rgettman

「容量」キーワードは定義上正しくないため、通常予想される方法では使用されません。

デフォルトでは、HashMapの「負荷係数」は0.75です。これは、HashMapのエントリ数が指定された容量の75%に達すると、配列のサイズを変更して再ハッシュすることを意味します。

たとえば、私がした場合:

Map<Integer, Integer> map = new HashMap<>(100);

75番目のエントリを追加すると、マップはエントリテーブルのサイズを2 * map.size()(または2 * table.length)に変更します。だから私たちはいくつかのことができます:

  1. 負荷係数を変更します-これはマップのパフォーマンスに影響を与える可能性があります
  2. 初期容量をlist.size()/ 0.75 + 1に設定します

最良のオプションは2つのうちの後者です。ここで何が行われているのかを説明しましょう。

list.size() / 0.75

これにより、list.size()+ list.size()の25%が返されます。たとえば、リストのサイズが100の場合は133が返されます。次に、マップのサイズが初期容量の75%に等しいため、サイズが100のリストがある場合、初期容量を134に設定します。これは、リストから100エントリすべてを追加してもマップのサイズ変更が発生しないことを意味します。

最終結果:

Map<Integer, Integer> map = new HashMap<>(list.size() / 0.75 + 1);
14
Jack Hopner

Guavaの Maps.newHashMapWithExpectedSize は、このヘルパーメソッドを使用して、予想される値の数に基づいて、デフォルトの負荷係数0.75の初期容量を計算します。

/**
 * Returns a capacity that is sufficient to keep the map from being resized as
 * long as it grows no larger than expectedSize and the load factor is >= its
 * default (0.75).
 */
static int capacity(int expectedSize) {
    if (expectedSize < 3) {
        checkArgument(expectedSize >= 0);
        return expectedSize + 1;
    }
    if (expectedSize < Ints.MAX_POWER_OF_TWO) {
        return expectedSize + expectedSize / 3;
    }
    return Integer.MAX_VALUE; // any large value
}

参照: ソース

newHashMapWithExpectedSizeドキュメントから:

HashMapインスタンスを作成し、それが十分な「初期容量」でshouldexpectedSize要素を拡張せずに保持します。この動作は広く保証されていませんが、OpenJDK 1.6に当てはまることが確認されています。また、メソッドが誤っていないことも保証されませんoversizing返されたマップ。

12
Paul Bellora

あなたがやっていることは結構です。このようにして、ハッシュマップに、少なくとも初期値に対して十分な容量があることを確認します。ハッシュマップの使用パターンに関する詳細情報がある場合(例:頻繁に更新されますか?多くの新しい要素が頻繁に追加されますか?)、より大きな初期容量を設定することができます(たとえば、list.size() * 2 )、しかし決して低くなることはありません。プロファイラーを使用して、初期容量がすぐに不足していないかどうかを判別します。

[〜#〜]更新[〜#〜]

初期のサイズ変更を回避するために初期容量を(int)Math.ceil(list.size() / loadFactor)(通常、デフォルトの負荷係数は0.75)に設定する必要があることを示唆してくれた@PaulBelloraに感謝します。

12
Óscar López

Java.util.HashMapのリファレンスドキュメント によると:

マップ内の予想されるエントリ数とその負荷係数は、初期容量を設定するときに考慮に入れて、再ハッシュ操作の数を最小限に抑える必要があります。初期容量がエントリの最大数を負荷係数で割った値よりも大きい場合、再ハッシュ操作は発生しません。

つまり、HashMapが保存する必要があるエントリの数が事前にわかっている場合は、適切な初期容量と負荷係数を選択することで、再ハッシュを防ぐことができます。しかしながら:

一般的なルールとして、デフォルトの負荷係数(.75)は、時間とスペースのコスト間の適切なトレードオフを提供します。値を大きくすると、スペースのオーバーヘッドは減少しますが、ルックアップコストは増加します(getおよびputを含むHashMapクラスのほとんどの操作で反映されます)。

4
Oswald

負荷係数/内部容量がわからない場合の経験則:

initialCapacityToUse = (Expected No. of elements in map / 0.75) + 1

この初期容量値を使用すると、予想されるnoが与えられた場合、格納の再ハッシュは行われません。マップの要素の。

0