web-dev-qa-db-ja.com

HashMap初期化パラメーター(ロード/初期容量)

N個のアイテムに対して効率的なHashMap/HashMapベースの構造を作成するには、どの値を渡す必要がありますか?

ArrayListでは、効率的な数はNです(Nはすでに将来の成長を想定しています)。 HashMapのパラメーターは何ですか? ((int)(N * 0.75d)、0.75d)?もっと?もっと少なく?負荷係数を変更するとどのような影響がありますか?

37
Ran Biron

負荷係数については、 HashMap javadoc から簡単に引用します。

一般的なルールとして、デフォルトの負荷係数(.75)は、時間とスペースのコスト間の適切なトレードオフを提供します。値を大きくすると、スペースのオーバーヘッドは減少しますが、ルックアップコストは増加します(getおよびputを含むHashMapクラスのほとんどの操作で反映されます)。マップ内の予想されるエントリ数とその負荷係数は、初期容量を設定するときに考慮に入れて、再ハッシュ操作の数を最小限に抑える必要があります。初期容量がエントリの最大数を負荷係数で割った値よりも大きい場合、再ハッシュ操作は発生しません。

つまり、特定の最適化を行う予定がない限り、負荷係数を.75から変更しないでください。初期容量は、変更したい唯一のものであり、N値、つまり(N / 0.75) + 1またはその領域の何かに従って設定します。これにより、テーブルが常に十分に大きくなり、再ハッシュが発生しなくなります。

39
Yuval Adam

私はいくつかの 単体テスト を実行して、これらの回答が正しいかどうかを確認しました。

(int) Math.ceil(requiredCapacity / loadFactor);

最初の容量は、HashMapまたはHashtableのどちらを使用するかを指定するためです。 「必要なもの」とは、マップにrequiredCapacity要素を追加しても、ラップしている配列のサイズが変更されず、配列が必要以上に大きくならないことを意味します。デフォルトの負荷容量は0.75なので、このようにHashMapを初期化すると機能します。

... = new HashMap<KeyType, ValueType>((int) Math.ceil(requiredCapacity / 0.75));

HashSetは事実上HashMapのラッパーなので、同じロジックがそこでも適用されます。つまり、次のようにHashSetを効率的に構築できます。

.... = new HashSet<TypeToStore>((int) Math.ceil(requiredCapacity / 0.75));

@Yuval Adamの答えは、(requiredCapacity / 0.75)が2の累乗である場合を除いて、すべてのケースで正しいです。この場合、割り当てられるメモリが多すぎます。
HashMapのコンストラクター自体がマップ配列に2のべき乗のサイズを持たせたいという問題に対処するため、@ NotEdibleの答えは多くの場合、メモリを使いすぎます。

20
Mark Rhodes

Googleの guava libraries には、予想されるアイテム数に最適化されたHashMapを作成する関数があります。 newHashMapWithExpectedSize

ドキュメントから:

HashMapインスタンスを作成し、「初期容量」が十分に高いため、expectedSize要素を拡張せずに保持する必要があります...

17
linqu

また、小さい側にHashMapを設定すると、ハッシュの衝突が発生する可能性が高くなり、ルックアップが遅くなる可能性があります。したがって、マップの速度を本当に心配し、マップのサイズを気にする必要がない場合は、保持する必要のあるデータに対してマップを少し大きくし過ぎる価値があるかもしれません。メモリが安いので、私は通常、既知の数のアイテムのHashMapを

HashMap<Foo> myMap = new HashMap<Foo>(numberOfElements * 2);

反対することもできます。実際、私はこのアイデアを検証または破棄してもらいたいと思っています。

6
Zarkonnen

Yuvalの回答はHashtableに対してのみ正しいです。 HashMapは2のべき乗のバケットを使用するため、HashMapの場合、Zarkonnenは実際に正しいです。これはソースコードから確認できます。

  // Find a power of 2 >= initialCapacity
  int capacity = 1;
  while (capacity < initialCapacity)
  capacity <<= 1;

したがって、負荷係数0.75fはHashtableとHashMapで同じですが、初期容量n * 2を使用する必要があります。nはHashMapに格納する予定の要素の数です。これにより、最高速のget/put速度が保証されます。

4
NotEdible

HashMapのソースコードを参照すると役立ちます。

エントリの数がしきい値(容量*負荷係数)に達すると、再ハッシュが自動的に行われます。つまり、負荷係数が小さすぎると、エントリが大きくなるにつれて頻繁に再ハッシュが発生する可能性があります。

1
grayger

ArrayListでは、効率的な数はNです(Nはすでに将来の拡張を想定しています)。

ええ、そうではありません。ここであなたが言っていることを私が誤解しない限りは。整数をArraylistコンストラクターに渡すと、正確にそのサイズの基になる配列が作成されます。追加の要素が1つでも必要な場合は、次にadd()を呼び出すときに、ArrayListが基になる配列のサイズを変更する必要があるため、この呼び出しは通常よりも時間がかかります。

一方、Nの値について成長を考慮に入れて話している場合-はい、値がこれを上回らないことを保証できる場合は、そのようなArraylistコンストラクターを呼び出すのが適切です。そしてこの場合、ハンクが指摘したように、マップの類似のコンストラクターはNと1.0fになります。これは、たまたまNを超えたとしても合理的に実行されます(これが定期的に発生すると予想される場合は、初期サイズとしてより大きな数を渡すことをお勧めします)。

負荷係数は、ご存じない場合に備えて、マップの総容量の一部として、その容量が増加するポイントです。

編集:汎用マップの場合、負荷係数を0.75のままにしておくことをお勧めします。 1.0の負荷係数は、キーにシーケンシャルハッシュコード(シーケンシャル整数キーなど)が含まれている場合は見事に機能しますが、それ以外の場合はハッシュバケットとの衝突に遭遇する可能性が高く、一部の要素のルックアップに時間がかかります。必要以上にバケットを作成すると、この衝突の可能性が減少します。つまり、要素が独自のバケット内に存在し、最短時間で取得できる可能性が高くなります。ドキュメントが言うように、これは時間とスペースのトレードオフです。どちらかが特に重要である場合(時期尚早に最適化するのではなく、プロファイラーが示すように!)、それを強調できます。それ以外の場合は、デフォルトのままにします。

1
Andrzej Doyle

ListおよびMapの初期化のほとんどの場合、ListまたはMapを以下のサイズのパラメーターで作成しても安全です。

List<T>(numElements + (numElements / 2));
Map<T,T>(numElements + (numElements / 2));

これは 0.75 ルールだけでなく、 * 2 上記の操作。

1
lv2program

重要なシステムで非常に大きなHashMapの場合、初期容量を間違って取得することが非常に問題になる可能性があるため、マップを初期化する最適な方法を決定するための経験的な情報が必要になる場合があります。

CollectionSpy( collectionspy.com )は新しいJavaプロファイラであり、瞬く間にどのHashMapが再ハッシュを必要としているか、何回持っているかを確認できます。容量ベースのコンテナーコンストラクターに対する安全な初期容量引数を決定するための理想的なツールです。

0