HashMapの負荷係数の意味は何ですか?


232

HashMapには2つの重要なプロパティがsizeありload factorます。私はJavaのドキュメントを調べました0.75fが、それは初期負荷係数であると述べています。しかし、私はそれの実際の使用法を見つけることができません。

誰かが負荷係数を設定する必要があるさまざまなシナリオとは何か、さまざまなケースの理想的なサンプル値は何かを説明できますか?

回答:


266

ドキュメントはかなりよく、それを説明します:

HashMapのインスタンスには、そのパフォーマンスに影響を与える2つのパラメーターがあります。初期容量と負荷係数です。容量はハッシュテーブル内のバケットの数で、初期容量はハッシュテーブルが作成されたときの容量です。負荷係数は、ハッシュテーブルの容量が自動的に増加する前に、ハッシュテーブルがどの程度いっぱいになるかを測定するものです。ハッシュテーブルのエントリ数が負荷係数と現在の容量の積を超えると、ハッシュテーブルが再ハッシュされる(つまり、内部データ構造が再構築される)ため、ハッシュテーブルのバケット数は約2倍になります。

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

すべてのパフォーマンス最適化と同様に、時期尚早に(つまり、ボトルネックの場所に関するハードデータがない場合)最適化を回避することをお勧めします。


14
他の答えはcapacity = N/0.75、リハッシュを避けるために指定することを提案していますが、私の最初の考えはちょうど設定されましたload factor = 1。そのアプローチには欠点がありますか?なぜ要因が影響を与えロードするget()と、put()運用コスト?
スーパーミッチ2013年

19
エントリ数=容量のある負荷係数= 1のハッシュマップは、統計的にかなりの量の衝突があります(=複数のキーが同じハッシュを生成している場合)。衝突が発生すると、1つのバケット内に一致するエントリが1つ以上あるため、ルックアップ時間が増加します。一致するエントリは、キーが等しいかどうかを個別にチェックする必要があります。いくつかの詳細な計算:preshing.com/20110504/hash-collision-probabilities
atimb

8
@atimbをフォローしていません。loadsetプロパティは、ストレージサイズを増やすタイミングを決定するためにのみ使用されますか?-ロードセットを1にすると、ハッシュ衝突の可能性がどのように増加しますか?-ハッシュアルゴリズムは、マップ内のアイテム数や、新しいストレージ「バケット」を取得する頻度などを認識していません。同じサイズのオブジェクトのセットについては、格納方法に関係なく、同じハッシュ値が繰り返される確率...
BrainSlugs83

19
マップのサイズが大きいほど、ハッシュの衝突の可能性は低くなります。たとえば、マップのサイズが4の場合、ハッシュコード4、8、16、32の要素は同じバケットに配置されますが、マップのサイズが32を超える場合、すべてのアイテムが独自のバケットを取得します。この例では、初期サイズ4と負荷係数1.0(4バケットですが、1つのバケット内の4要素すべて)のマップは、負荷係数0.75(8バケット、2バケットが満たされた)のマップよりも平均で2倍遅くなります-要素「4」および要素「8」、「16」、「32」を使用)。
2014年

1
@Adelinのルックアップコストは、負荷値が高いほど高くなります。これは、値が高いほど衝突が多くなるためです。Javaが衝突を処理する方法は、データ構造を使用して同じバケットに同じハッシュコードのアイテムを配置することです。Java 8以降、このデータ構造はバイナリ検索ツリーです。これにより、追加されたすべての要素がたまたま同じハッシュコードを持つ場合に発生する非常に最悪のケースで、ルックアップの最悪のケースの時間の複雑さO(lg(n))が作成されます。
ジジバイテ

141

HashMapテイクのデフォルトの初期容量は16で、負荷係数は0.75f(つまり、現在のマップサイズの75%)です。負荷係数は、HashMap容量を2倍にするレベルを表します。

たとえば、容量と負荷係数の積は16 * 0.75 = 12です。これは、12番目のキーと値のペアをに格納した後HashMap、その容量が32になることを表しています。


3
答えは明らかですが、12個のキーと値のペアを格納した直後に容量が32になるのか、13番目のエントリが追加されたときに容量が変化してからエントリが挿入されるのかを教えてください。
userab

それはバケットの数が2増えることを意味しますか?
LoveMeow 2017

39

実際、私の計算では、「完全な」負荷係数はlog 2(〜0.7)に近くなっています。ただし、これよりも小さい負荷係数はパフォーマンスが向上します。おそらく0.75は帽子から引き抜かれたと思います。

証明:

バケットが空かどうかを予測することで、連鎖を回避し、分岐予測を利用できます。空になる確率が.5を超える場合、バケットはおそらく空です。

sがサイズを表し、nが追加されたキーの数を表すとします。二項定理を使用すると、バケットが空になる確率は次のとおりです。

P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)

したがって、以下の場合、バケットはおそらく空です。

log(2)/log(s/(s - 1)) keys

sが無限大に達し、追加されたキーの数がP(0)= .5である場合、n / sはすぐにlog(2)に近づきます。

lim (log(2)/log(s/(s - 1)))/s as s -> infinity = log(2) ~ 0.693...

4
数学オタクFTW!そう.75最寄りの分数を理解するのは簡単に丸めたlog(2)マジックナンバーの少ないような、そしてルックス。D:私は、その実装上で述べたコメントで、JDKのデフォルト値への更新を見てみたい
デコードされた

2
私は本当にこの回答が好きですが、私はJavaEE開発者です。つまり、数学は決して私の強力なスーツではなかったので、あなたが書いたことはほとんど理解できませんlol
searchengine27

28

負荷係数とは何ですか?

HashMapの容量を増やすために使い果たされる容量の量は?

なぜ負荷係数ですか?

負荷係数はデフォルトで初期容量の0.75(16)であるため、容量が増加する前にバケットの25%が解放されます。これにより、新しいハッシュコードを指す多くの新しいバケットが、増加した直後に存在するようになります。バケットの数。

では、なぜ多くの無料バケットを保持する必要があるのでしょうか?無料バケットを維持することがパフォーマンスに与える影響は何ですか?

負荷係数を1.0に設定すると、非常に興味深いことが起こります。

hashCodeが888であるハッシュマップにオブジェクトxを追加していて、ハッシュマップでハッシュコードを表すバケットが空いているとすると、オブジェクトxはバケットに追加されますが、ここで、hashCodeがまた、888の場合、オブジェクトyは必ずバケットの最後に追加されます(バケットはキー、値、および次を格納するlinkedList実装にすぎないため)。これにより、パフォーマンスに影響があります。あなたので、オブジェクトのy、あなたが検索を実行した場合、時間が取らバケットの頭の中ではもはや存在しないことになるだろうされていないO(1)今回はそれが同じバケットにあるアイテムの数に依存します。これはハッシュ衝突と呼ばれ、負荷係数が1未満の場合にも発生します。

パフォーマンス、ハッシュ衝突、負荷係数の相関関係は?

負荷係数が低い =空きバケットが多い= 衝突の可能性が少ない =高いパフォーマンス=高いスペース要件。

どこか間違っている場合は修正してください。


2
hashCodeが1- {countバケット}の範囲の数値にどのように取り除かれるかについて少し追加することができます。そのため、バケットの数自体はわかりませんが、ハッシュアルゴリズムの最終結果は、より広い範囲。HashCodeは完全なハッシュアルゴリズムではなく、簡単に再処理できるほど小さいものです。したがって、すべての要素を同じバケットに格納できるため、「無料バケット」という概念はありませんが、「無料バケットの最小数」という概念があります。むしろ、それはあなたのハッシュコードのキースペースであり、それはcapacity *(1 / load_factor)に等しいです。40要素、0.25負荷係数= 160バケット。
user1122069

私はからのオブジェクトのルックアップの時間を考えるLinkedListと呼ばれているAmortized Constant Execution Timeとして表記+などO(1)+
のRaf

19

ドキュメントから:

負荷係数は、ハッシュテーブルの容量が自動的に増加する前に、ハッシュテーブルがどの程度いっぱいになるかを測定するものです

これは実際には特定の要件に依存します。初期負荷係数を指定するための「経験則」はありません。


ドキュメンテーションも言う; 「一般的なルールとして、デフォルトの負荷係数(.75)は、時間とスペースのコスト間の適切なトレードオフを提供します。」したがって、不明な場合は、デフォルトで大まかに設定します。
ferekdoley 2017

4

HashMap DEFAULT_INITIAL_CAPACITY = 16およびDEFAULT_LOAD_FACTOR = 0.75fの 場合、これはHashMap内のすべてのエントリの最大数= 16 * 0.75 = 12であることを意味します。13番目の要素が追加されると、HashMapの容量(配列サイズ)が2倍になります!完璧なイラストがこの質問に答えました: ここに画像の説明を入力してください 画像はここから取得されます:

https://javabypatel.blogspot.com/2015/10/what-is-load-factor-and-rehashing-in-hashmap.html


2

バケットが一杯になりすぎた場合は、

非常に長いリンクリスト。

そして、それは要点を打ち負かすようなものです。

これが4つのバケットがある例です。

今のところHashSetには象とアナグマがいます。

これはかなり良い状況ですよね?

各要素には0個または1個の要素があります。

次に、HashSetに2つの要素を追加します。

     buckets      elements
      -------      -------
        0          elephant
        1          otter
         2          badger
         3           cat

これも悪くありません。

各バケットには1つの要素しかありません。だから私が知りたいなら、これにはパンダが含まれていますか?

バケット番号1をすぐに確認できますが、

そこと

私はそれが私たちのコレクションにないことを知っていました。

猫がいるかどうか知りたいならバケツを見る

3番

私は猫を見つけました。

コレクション。

コアラを追加したらどうでしょう。

             buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala 
         2          badger
         3           cat

たぶん今ではバケット番号1ではなく見ているだけではなく

1つの要素

私は2つを見る必要があります。

しかし、少なくとも私は象、アナグマを見る必要はありません。

ネコ。

私が再びパンダを探しているなら、それはバケツにしか入れることができません

ナンバー1と

私はカワウソ以外のものを見る必要はありません

コアラ。

しかし今、私はアリゲーターをバケツ番号1に入れ、あなたはできる

多分これがどこに行くのか見てください。

バケット番号1がどんどん大きくなり続け、

より大きく、それから私は基本的にすべてを調べる必要があります

検索する要素

バケット番号1にあるはずのもの。

            buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala ->alligator
         2          badger
         3           cat

他のバケットに文字列を追加し始めたら、

そうです、問題はどんどん大きくなっています

単一のバケツ。

バケットがいっぱいになりすぎないようにするにはどうすればよいですか?

ここでの解決策は

          "the HashSet can automatically

        resize the number of buckets."

バケットが取得されていることをHashSetが認識している

満杯。

それは、このすべての1つのルックアップのこの利点を失っています

要素。

そして、それはより多くのバケットを作成するだけです(通常、以前のように2回)。

次に、要素を正しいバケットに配置します。

だからここに私たちの基本的なHashSetの実装があります

連鎖。次に、「自己サイズ変更HashSet」を作成します。

このHashSetは、バケットが

いっぱいになりすぎて

より多くのバケットが必要です。

loadFactorはHashSetクラスの別のフィールドです。

loadFactorは、あたりの要素の平均数を表します

バケツ、

その上でサイズを変更します。

loadFactorは、空間と時間のバランスです。

バケットがいっぱいになると、サイズが変更されます。

もちろんそれには時間がかかりますが、

バケットが

もう少し空です。

例を見てみましょう。

これがHashSetです。これまでに4つの要素を追加しました。

象、犬、猫、魚。

          buckets      elements
      -------      -------
        0          
        1          elephant
         2          cat ->dog
         3           fish
          4         
           5

この時点で、loadFactor、

しきい値、

大丈夫なバケットあたりの平均要素数

とは、0.75です。

バケットの数はbuckets.lengthで、6です。

この時点で、HashSetには4つの要素があるため、

現在のサイズは4です。

HashSetのサイズを変更します。つまり、バケットを追加します。

バケットあたりの平均要素数が

loadFactor。

これは、現在のサイズをbuckets.lengthで割った値が

loadFactorより大きい。

この時点で、バケットごとの平均要素数

4を6で割った値です。

4つの要素、6つのバケット、つまり0.67です。

それは私が設定した0.75のしきい値よりも小さいので、

はい。

サイズを変更する必要はありません。

しかし、今度はウッドチャックを追加するとします。

                  buckets      elements
      -------      -------
        0          
        1          elephant
         2        woodchuck-> cat ->dog
         3           fish
          4         
           5

ウッドチャックはバケット番号3になります。

この時点で、currentSizeは5です。

そしてバケットごとの要素の平均数

currentSizeをbuckets.lengthで割った値です。

つまり、5つの要素を6つのバケットで割ると0.83になります。

そして、これは0.75であったloadFactorを超えています。

この問題に対処するために、

たぶん少し

より空になるので、

バケットには

要素は少し複雑ではありません、サイズを変更したいです

私のHashSet。

HashSetのサイズ変更には2つのステップがあります。

まず、バケットの数を2倍にします。6つのバケットがありましたが、

今度は12個のバケットを用意します。

ここで、0.75に設定したloadFactorは同じままです。

ただし、変更されたバケットの数は12です。

同じままの要素の数は5です。

5を12で割ると、約0.42になります。

負荷率、

今は大丈夫です

しかし、これらの要素の一部が含まれているため、完了していません

今間違ったバケツ。

たとえば、象。

ゾウはバケット数2でした。

象のキャラクター

8でした。

バケットは6つあり、8-6は2です。

それが2位に終わった理由です。

しかし、12個のバケットがあるので、8 mod 12は8なので、

象はバケット番号2にもう属していません。

象はバケット番号8に属しています。

ウッドチャックはどうですか?

ウッドチャックは、この全体の問題を引き起こしたものでした。

ウッドチャックはバケット3で終了しました。

9 mod 6は3だからです。

しかし、今は9 mod 12を行います。

9 mod 12は9、woodchuckはバケット番号9に移動します。

そして、あなたはこれらすべての利点を理解しています。

バケット番号3には2つの要素しかありませんが、以前は3でした。

これがコードです。

HashSetに個別のチェーンを配置したところ

サイズ変更は行いませんでした。

これが、サイズ変更を使用する新しい実装です。

このコードのほとんどは同じですが、

私たちはまだそれが含まれているかどうかを決定するつもりです

すでに値。

そうでない場合は、どのバケットにあるかを特定します

に入る必要があります

それをそのバケットに追加し、そのLinkedListに追加します。

しかし、今度はcurrentSizeフィールドを増分します。

currentSizeは、数値を追跡するフィールドでした

HashSetの要素の。

それをインクリメントしてから見ていきます

平均負荷で

バケットあたりの平均要素数。

ここで分割を行います。

確認するには、ここで少しキャストする必要があります

ダブルになること。

次に、その平均負荷をフィールドと比較します

私が設定したこと

たとえば、このHashSetを作成した0.75は、

loadFactor。

平均負荷がloadFactorよりも大きい場合、

つまり、バケットごとの要素が多すぎます

平均、そして私は再挿入する必要があります。

だからここに再挿入するメソッドの実装があります

すべての要素。

まず、oldBucketsというローカル変数を作成します。

彼らは現在立っているバケットを参照しています

すべてのサイズを変更する前に。

リンクリストの新しい配列はまだ作成していません。

バケットの名前をoldBucketsに変更しているだけです。

バケツがクラスのフィールドだったことを思い出してください。

新しい配列を作成するには

リンクされたリストの2倍の要素が含まれます

最初と同じように。

今、私は実際に再挿入を行う必要があります、

すべての古いバケットを繰り返し処理します。

oldBucketsの各要素は、文字列のLinkedListです

それはバケツです。

そのバケットを通過して、その中の各要素を取得します

バケツ。

そして、今度はそれをnewBucketsに再挿入します。

hashCodeを取得します。

それがどのインデックスかを調べます。

そして今、私は新しいバケット、新しいLinkedListを取得します

文字列と

新しいバケットに追加します。

要約すると、これまで見てきたHashSetはLinkedの配列です

リスト、またはバケット。

自己サイズ変更HashSetは、一定の比率または


1

私はn * 1.5またはn +(n >> 1)のテーブルサイズを選択します。これにより、除算なしで.66666〜の負荷係数が得られます。これは、ほとんどのシステム、特に除算のないポータブルシステムでは遅いですハードウェア。

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