l Hollisによる
この記事はHollisの許可を得て複製されています(ID:hollischuang)
Javaの基盤では、コレクションクラスは重要な知識であり、日常の開発でよく使用されます。たとえば、ListとMapもコードで非常に一般的です。
個人的には、JDKのエンジニアはHashMapの実装に関して多くの最適化を行ったと思います。すべてのJDKソースコードのどのクラスが最も卵数が多いかを言えば、HashMapは少なくとも上位5つにランク付けできると思います。
このため、多くの詳細が見過ごされがちですが、今日は、次の問題の1つに注意を払います。
HashMapの負荷係数が1または0.5ではなく0.75に設定されているのはなぜですか?この背後にある考慮事項は何ですか?
負荷率はHashMapおよびハイエンドインタビューの一般的なテストサイトで非常に重要な概念であるため、この問題を過小評価しないでください。
さらに、これは設定する価値があり、一部の人々はそれを間違って使用します。たとえば、私の「Alibaba Java開発マニュアル」は、数日前にHashMapを作成するときに初期容量を設定することを推奨しましたが、どれくらいが適切ですか?「この記事では、一部の読者が次のように答えました。

誰かが負荷係数を変更しようとするので、それを1に変更するのが適切ですか?HashMapが負荷係数のデフォルト値として1を使用しないのはなぜですか?
loadFactorとは
最初に、負荷係数(loadFactor)とは何かを紹介しましょう。読者がすでにこの部分を知っている場合は、この段落を直接スキップできます。
HashMapが初めて作成されるときに、その容量が指定されることがわかっています(明示的に指定されていない場合、デフォルトは16 です。なぜHashMapのデフォルトの容量が16であるのかを確認してください)。それを超えると、容量を超える可能性があるため、容量拡張メカニズムが必要になります。
いわゆる拡張とは、HashMapの容量を拡張することです。
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
場合我々は、プロセス内のHashMapに要素を追加し、コードから見ることができる元素个数(size)超过临界值(threshold)
時間が自動拡張(リサイズ)、および、拡張後、だけでなく、オリジナルの要素は焼き直しのHashMapの必要性、今後の本来のバケットになります要素は新しいバケットに再配布されます。
HashMapでは、しきい値(threshold)=負荷係数(loadFactor)*容量(capacity)。
loadFactorは負荷係数であり、HashMapの完全性を示します。デフォルト値は0.75fです。つまり、デフォルトでは、HashMapの要素数が容量の3/4に達すると、自動的に拡張されます。
拡大する理由
前に言ったように、HashMapは拡張プロセス中にその容量を拡張する必要があるだけでなく、再ハッシュする必要もあります!したがって、このプロセスは実際には非常に時間がかかり、マップ内の要素が多いほど時間がかかります。
再ハッシュのプロセスは、その中のすべての要素を再ハッシュし、そのバケットに割り当てられるように再計算することと同じです。
それで、誰かが質問について考えましたか?それはとても面倒なので、なぜ容量を拡張するのですか?HashMapは配列リンクリストではありませんか?展開されていない場合は、無期限に保存することもできます。なぜ容量を拡張するのですか?
これは実際にはハッシュの衝突に関連しています。
ハッシュ衝突
HashMapは実際にはハッシュ関数に基づいて実装されていますが、ハッシュ関数には次の基本的な特性があります。同じハッシュ関数に従って計算されたハッシュ値が異なる場合、入力値は異なる必要があります。ただし、同じハッシュ関数に従って計算されたハッシュ値が同じである場合、入力値は必ずしも同じではありません。
2つの異なる入力値、同じハッシュ関数に従って計算された同じハッシュ値は、衝突と呼ばれます。
ハッシュ関数の品質を測定する重要な指標は、衝突の確率と衝突の解決策です。
ハッシュの衝突を解決するには多くの方法がありますが、最も一般的な方法はチェーンアドレスメソッドです。これはHashMapでも使用されている方法です。
HashMapは、配列とリンクリストを組み合わせて2つを利用し、リンクリストの配列として理解できます。
HashMapは、リンクされたリストの配列のデータ構造に基づいています。
HashMapに要素を配置するときは、最初に配列内のどのリンクリストを見つけてから、この要素をリンクリストの後ろに掛ける必要があります。
HashMapから要素を取得するときは、配列内のリンクリストを特定し、必要な要素が見つかるまでリンクリストの要素を1つずつトラバースする必要があります。
ただし、HashMapでの競合が高すぎる場合、配列のリンクリストはリンクリストに退化します。このとき、クエリの速度は大幅に低下します。
したがって、HashMapの読み取り速度を保証するために、HashMapの競合が高すぎないことを保証する方法を見つける必要があります。
ハッシュ衝突を回避するための容量拡張
では、どうすればハッシュの衝突を効果的に回避できるでしょうか。
逆に考えてみましょう。HashMapでハッシュの衝突が増える原因は何だと思いますか?
2つの状況のみがあります。
1.容量が小さすぎる。容量が小さいほど、衝突の可能性が高くなります。オオカミの肉が減ると、スクランブルが発生します。
2.ハッシュアルゴリズムは十分ではありません。アルゴリズムが合理的でない場合は、同じバケットまたは複数のバケットに分割される可能性があります。不均一な分布も競合につながります。
したがって、HashMapでのハッシュの衝突の解決も、これら2つの側面から始まります。
これら2つのポイントはHashMapによく反映されています。2つの方法を組み合わせると、適切なときに配列の容量が拡張され、適切なハッシュアルゴリズムによって要素が割り当てられる配列が計算されます。これにより、競合の可能性を大幅に減らすことができます。クエリの効率が低いという問題を回避できます。
デフォルトのloadFactorが0.75である理由
この時点で、loadFactorはHashMapの重要な概念であり、彼はこのHashMapの最大の完全性を表しています。
ハッシュの衝突を回避するために、HashMapは適切なときに拡張する必要があります。つまり、その中の要素の数がクリティカル値に達し、このクリティカル値がloadFactorに関連している場合、つまり、妥当なloadFactorを設定すると、ハッシュの競合を効果的に回避できます。
では、loadFactorの適切な設定は何ですか?
この値は、JDKのソースコードで0.75になりました。
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
では、なぜ0.75を選択するのでしょうか。背後にある考慮事項は何ですか?なぜ0.8ではなく1ではないのですか?0.5ではなく0.75?
JDKの公式ドキュメントには、そのような説明があります:
一般的なルールとして、デフォルトの負荷係数(.75)は、時間とスペースのコスト間の適切なトレードオフを提供します。値を大きくすると、スペースのオーバーヘッドは減少しますが、ルックアップコストは増加します(getおよびputを含むHashMapクラスのほとんどの操作で反映されます)。
それはおそらく意味します:一般的に言えば、デフォルトの負荷係数(0.75)は、時間とスペースのコストの間の適切なトレードオフを提供します。値を大きくすると、スペースのオーバーヘッドは減少しますが、検索コストが増加します(getおよびputを含むHashMapクラスのほとんどの操作で反映されます)。
負荷係数を1に設定し、デフォルトの初期値16を使用するとしたら、拡張する前にHashMapを「完全」にする必要があることを想像してください。
次に、HashMapでは、ハッシュアルゴリズムの後、これらの16の要素が16の異なるバケットに分類されることが最良のケースです。そうしないと、ハッシュの衝突が必ず発生します。また、要素が多いほど、ハッシュの衝突の確率が高くなり、検索速度が低下します。
0.75の数学的基礎
さらに、数学的な考え方を使用して、この値がどれほど適切かを計算できます。
バケットが空で空でない確率は0.5であると想定します。容量にはsを、追加された要素の数にはnを使用します。
追加されたキーのサイズとn個のキーの数をsとしましょう。二項定理によれば、バケットが空である確率は次のとおりです。
P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)
したがって、バケット内の要素の数が次の値より少ない場合、バケットは空になる可能性があります。
log(2)/log(s/(s - 1))
sが無限大になる傾向がある場合、キーの数を増やしてP(0)= 0.5にすると、n / sはすぐにlog(2)に近づきます。
log(2) ~ 0.693...
したがって、妥当な値は約0.7です。
もちろん、この数学的計算方法はJavaの公式ドキュメントには反映されておらず、このレイヤーが考慮されているかどうかを調査する方法はありません。記事を書いているときにLu Xunが何を考えたかわからないように、推測することしかできません。この推測はスタックオーバーフロー(https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap)から来ています
不可避係数0.75
理論的には、負荷係数を大きくしすぎることはできないと考えます。そうしないと、ハッシュの競合が多数発生したり、小さすぎたりして、スペースが無駄になります。
数学的推論により、この値が約0.7であると計算するのが妥当です。
それで、結局なぜ0.75を選んだのですか?
以前に式について説明したときのことを思い出してください临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)
。
「HashMapのデフォルト容量が16である理由」「導入」されたHashMapの拡張メカニズムによると、彼は容量の値が常に2のべき乗であることを保証します。
次に、負荷係数(loadFactor)*容量(capacity)の結果が整数であることを保証するために、この値と2のべき乗の結果は整数であるため、この値は0.75(3/4)がより妥当です。
まとめ
HashMapはKV構造であり、クエリと挿入の速度を向上させるために、最下層はリンクリストの配列などのデータ構造を使用しています。
ただし、要素の位置を計算するときは、ハッシュアルゴリズムを使用する必要があります。HashMapで使用されるハッシュアルゴリズムは、チェーンアドレス方式です。この方法には2つの極端な方法があります。
HashMapでのハッシュの衝突の確率が高い場合、HashMapはリンクリストに縮退します(実際には縮退ではなく、リンクリストの直接操作のように動作します)。リンクリストの最大の欠点は、クエリ速度が比較的遅いことです。テーブルヘッダーは1つずつ移動し始めます。
したがって、HashMapでの多数のハッシュの競合を回避するために、適切なときに拡張する必要があります。
展開の条件は、要素数が臨界値に達したときです。HashMapでの臨界値の計算方法:
临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)
負荷係数は、アレイが到達できる最大の満杯を表します。この値は大きすぎても小さすぎてもいけません。
loadFactorが大きすぎる場合、たとえば1に等しい場合は、ハッシュの衝突が発生する可能性が高く、クエリの速度が大幅に低下します。
loadFactorが小さすぎる(たとえば0.5に等しい)ため、頻繁に拡張してもスペースが無駄になりません。
したがって、この値は0.5から1の間である必要があります。数式に従って計算します。ログ(2)の場合、この値はより妥当です。
さらに、拡張効率を向上させるために、HashMapの容量(容量)には一定の要件があります。つまり、2の累乗でなければなりません。
したがって、loadFactorが3/4の場合、容量と容量の積は整数になります。
したがって、通常の状況では、特別な理由がない限り、loadFactorの値を変更することはお勧めしません。
たとえば、私のマップは5 kvしか保存せず、決して変更されないことを明確に知っているので、loadFactorの指定を検討できます。
しかし、実際にはお勧めしません。容量を指定することでこれを実現できます。詳細については、Alibaba Java開発マニュアルを参照してくださいHashMapの作成時に初期容量を設定することをお勧めしますが、どれくらいが適切ですか。
参考資料:
https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap
https://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html
https://preshing.com/20110504/hash-collision-probabilities/
著者について:コーディングを独自に追求している人であるホリスは、現在アリババのテクノロジーエキスパートであり、パーソナルテクノロジーブロガーであり、ネットワーク全体で数千万もの技術記事を読んでいます。
よりエキサイティングな推奨事項
☞Mr。デジタル変革は難しすぎますか?AI、IoTが大打撃!
☞ 対話ロボットをすばやく構築し、このトリックを使用してください!
☞ 新しい流行防止策:WHO、IBM、Oracle、Microsoftがオープンデータブロックチェーンプロジェクトを構築しました!
☞ 発生後、世界の暗号化コミュニティはコロナウイルスと戦うために非常に多くのことをしました!
☞現代オタクたちの【テクニカルベーン】と言われていますが......
あなたが注文するすべての「ウォッチング」、私はそれを真剣に受け止めます