回答:
ドキュメントはかなりよく、それを説明します:
HashMapのインスタンスには、そのパフォーマンスに影響を与える2つのパラメーターがあります。初期容量と負荷係数です。容量はハッシュテーブル内のバケットの数で、初期容量はハッシュテーブルが作成されたときの容量です。負荷係数は、ハッシュテーブルの容量が自動的に増加する前に、ハッシュテーブルがどの程度いっぱいになるかを測定するものです。ハッシュテーブルのエントリ数が負荷係数と現在の容量の積を超えると、ハッシュテーブルが再ハッシュされる(つまり、内部データ構造が再構築される)ため、ハッシュテーブルのバケット数は約2倍になります。
一般的なルールとして、デフォルトの負荷係数(.75)は、時間とスペースのコストの間の適切なトレードオフを提供します。値を大きくすると、スペースのオーバーヘッドは減少しますが、ルックアップコストは増加します(getおよびputを含むHashMapクラスのほとんどの操作で反映されます)。マップ内の予想されるエントリ数とその負荷係数は、初期容量を設定するときに考慮に入れて、再ハッシュ操作の数を最小限に抑える必要があります。初期容量がエントリの最大数を負荷係数で割った値より大きい場合、再ハッシュ操作は発生しません。
すべてのパフォーマンス最適化と同様に、時期尚早に(つまり、ボトルネックの場所に関するハードデータがない場合)最適化を回避することをお勧めします。
HashMap
テイクのデフォルトの初期容量は16で、負荷係数は0.75f(つまり、現在のマップサイズの75%)です。負荷係数は、HashMap
容量を2倍にするレベルを表します。
たとえば、容量と負荷係数の積は16 * 0.75 = 12
です。これは、12番目のキーと値のペアをに格納した後HashMap
、その容量が32になることを表しています。
実際、私の計算では、「完全な」負荷係数は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...
.75
最寄りの分数を理解するのは簡単に丸めたlog(2)
マジックナンバーの少ないような、そしてルックス。D:私は、その実装上で述べたコメントで、JDKのデフォルト値への更新を見てみたい
HashMapの容量を増やすために使い果たされる容量の量は?
負荷係数はデフォルトで初期容量の0.75(16)であるため、容量が増加する前にバケットの25%が解放されます。これにより、新しいハッシュコードを指す多くの新しいバケットが、増加した直後に存在するようになります。バケットの数。
負荷係数を1.0に設定すると、非常に興味深いことが起こります。
hashCodeが888であるハッシュマップにオブジェクトxを追加していて、ハッシュマップでハッシュコードを表すバケットが空いているとすると、オブジェクトxはバケットに追加されますが、ここで、hashCodeがまた、888の場合、オブジェクトyは必ずバケットの最後に追加されます(バケットはキー、値、および次を格納するlinkedList実装にすぎないため)。これにより、パフォーマンスに影響があります。あなたので、オブジェクトのy、あなたが検索を実行した場合、時間が取らバケットの頭の中ではもはや存在しないことになるだろうされていないO(1)今回はそれが同じバケットにあるアイテムの数に依存します。これはハッシュ衝突と呼ばれ、負荷係数が1未満の場合にも発生します。
負荷係数が低い =空きバケットが多い= 衝突の可能性が少ない =高いパフォーマンス=高いスペース要件。
どこか間違っている場合は修正してください。
LinkedList
と呼ばれているAmortized Constant Execution Time
として表記+
などO(1)+
ドキュメントから:
負荷係数は、ハッシュテーブルの容量が自動的に増加する前に、ハッシュテーブルがどの程度いっぱいになるかを測定するものです
これは実際には特定の要件に依存します。初期負荷係数を指定するための「経験則」はありません。
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
バケットが一杯になりすぎた場合は、
非常に長いリンクリスト。
そして、それは要点を打ち負かすようなものです。
これが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は、一定の比率または
私はn * 1.5またはn +(n >> 1)のテーブルサイズを選択します。これにより、除算なしで.66666〜の負荷係数が得られます。これは、ほとんどのシステム、特に除算のないポータブルシステムでは遅いですハードウェア。
capacity = N/0.75
、リハッシュを避けるために指定することを提案していますが、私の最初の考えはちょうど設定されましたload factor = 1
。そのアプローチには欠点がありますか?なぜ要因が影響を与えロードするget()
と、put()
運用コスト?