オリジナル|コレクションを理解していると言ったのですが、インタビュアーから、HashMapの負荷率が1に設定されていない理由が尋ねられました。?

オリジナル|コレクションを理解していると言ったのですが、インタビュアーから、HashMapの負荷率が1に設定されていない理由が尋ねられました。

△コーディングを独自に追求する
オリジナル|コレクションを理解していると言ったのですが、インタビュアーから、HashMapの負荷率が1に設定されていない理由が尋ねられました。 ?
ホリスこれはホリスの254番目のオリジナル共有
作者ですlホリス
ソースlホリス(ID:hollischuang)
Javaの基盤において、コレクションクラスは非常に重要な知識であり、日々の開発でもあります。よく使われます たとえば、リストとマップもコードで非常に一般的です。
個人的には、JDKのエンジニアが実際にHashMapの実装のために多くの最適化を行ったと思います。すべてのJDKソースコードのどれが最も埋もれた卵を持っているかを言いたいのであれば、HashMapは少なくともトップ5になると思います。
多くの詳細が見落とされがちなのはまさにこのためです。今日は、問題の1つに焦点を当てます。つまり
、HashMapの負荷率が0.5ではなく1ではなく0.75に設定されているのはなぜですか。この背後にある考慮事項は何ですか?
負荷率はHashMapの非常に重要な概念であり、ハイエンドのインタビューの一般的なテストポイントであるため、この質問を過小評価しないでください。
また、これは設定する価値があり、間違って使用する人もいます。たとえば、「数日前のAlibaba Java開発マニュアルでは、HashMapを作成するときに初期容量を設定することを推奨していますが、どれだけ適切ですか?」「この記事では、一部の読者は次のように回答しました
オリジナル|コレクションを理解していると言ったのですが、インタビュアーから、HashMapの負荷率が1に設定されていない理由が尋ねられました。 ?
。

オリジナル|コレクションを理解していると言ったのですが、インタビュアーから、HashMapの負荷率が1に設定されていない理由が尋ねられました。 ?
負荷率を変更しようとする人もいるので、1に変更するのが適切ですか?HashMapが負荷係数のデフォルト値として1を使用しないのはなぜですか?

loadFactorとは何ですか

まず、負荷係数(loadFactor)とは何かを紹介しましょう。読者がこの部分をすでに知っている場合は、この段落を直接スキップできます。
HashMapが初めて作成されるときに、その容量が指定されることがわかっています(明示的に指定されていない場合、デフォルトは16です。HashMapのデフォルトの容量が16である理由を参照してください)。その後、HashMapに要素を配置し続けます。容量を超える場合は、拡張メカニズムが必要です。
いわゆる拡張は、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に要素を追加する過程で、要素の数(サイズ)がしきい値(しきい値)を超えると、自動拡張(サイズ変更)が実行され、拡張後にも必要であることがわかります。 HashMapの元の要素を再ハッシュします。つまり、元のバケットの要素を新しいバケットに再配布します。
HashMapでは、しきい値(しきい値)=負荷係数(loadFactor)*容量(容量)。
loadFactorは、HashMapがどれだけいっぱいであるかを示す負荷係数です。デフォルト値は0.75fです。これは、デフォルトで、HashMapの要素数が容量の3/4に達すると、自動的に拡張されることを意味します。(詳細については、HashMapで明確でない概念を参照してください)

なぜ拡大するのか

HashMapは、拡張プロセス中に容量を拡張する必要があるだけでなく、再ハッシュする必要があることを前に述べたことを思い出してください。したがって、このプロセスは実際には非常に時間がかかり、マップ内の要素が多いほど時間がかかります。
再ハッシュのプロセスは、その中のすべての要素を再ハッシュし、どのバケットに割り当てるかを再計算することと同じです。
それで、誰かが質問について考えたことがあります、それはとても面倒なので、なぜあなたは拡大する必要がありますか?HashMapは配列リンクリストではありませんか?拡張せずに、無限に保存できます。なぜ拡大するのですか?
これは実際にはハッシュの衝突に関連しています。
ハッシュ衝突

HashMapは実際にはハッシュ関数に基づいて下部に実装されていますが、ハッシュ関数には次の基本的な特性があります。同じハッシュ関数に従って計算されたハッシュ値が異なる場合、入力値も異なる必要があります。ただし、同じハッシュ関数に基づいて計算されたハッシュ値が同じである場合、入力値は同じでない可能性があります。
2つの異なる入力値が同じハッシュ関数から計算された同じハッシュ値を持つという現象は、衝突と呼ばれます。
ハッシュ関数の品質を測定するための重要な指標は、衝突の確率と衝突の解決策です。
ハッシュの衝突を解決するために、多くの方法がありますが、その中でより一般的なのはチェーンアドレス方法であり、これはHashMapで採用されている方法でもあります。詳細については、ネットワーク全体のMapのhash()の分析に関する最も詳細な記事を参照してください。
HashMapは、配列とリンクされたリストを組み合わせて、2つを利用します。これは、リンクされたリストの配列として理解できます。
オリジナル|コレクションを理解していると言ったのですが、インタビュアーから、HashMapの負荷率が1に設定されていない理由が尋ねられました。 ?
HashMapは、リンクされたリストの配列のデータ構造に基づいて実装されます。
HashMapに要素を配置するときは、最初に配列内のどのリンクリストを見つけてから、この要素をリンクリストの後ろに吊るす必要があります。
HashMapから要素を取得するときは、配列内のリンクされたリストを見つけて、必要な要素が見つかるまでリンクされたリスト内の要素を1つずつトラバースする必要もあります。
ただし、HashMapの競合が高すぎると、配列のリンクリストがリンクリストに縮退します。この場合、クエリ速度は大幅に低下します。
オリジナル|コレクションを理解していると言ったのですが、インタビュアーから、HashMapの負荷率が1に設定されていない理由が尋ねられました。 ?
したがって、HashMapの読み取り速度を確保するには、HashMapの競合が高すぎないようにする方法を見つける必要があります。
ハッシュの衝突を回避するためのスケーリング

では、どうすればハッシュの衝突を効果的に回避できるでしょうか。
最初に逆に考えてみましょう。HashMapでハッシュの衝突が増える原因は何だと思いますか?
2つの状況があります:
1。容量が小さすぎます。容量が小さいほど、衝突の可能性が高くなります。オオカミが多く肉が少なければ、競争が起こります。
2.ハッシュアルゴリズムは十分ではありません。アルゴリズムが不合理な場合は、同じバケットまたは複数のバケットに分割される可能性があります。不均一な配布も競争につながる可能性があります。
したがって、HashMapでのハッシュ衝突の解決も、これら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の公式ドキュメントには、次のような説明があります。

As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put).

大まかな意味は次のとおりです。一般的に、デフォルトの負荷係数(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が選択されたのでしょうか。
前に式、つまり、しきい値=負荷率容量(容量)

について説明したことを思い出してください「HashMapのデフォルトの容量が16であるのはなぜですか?「」で述べたように、HashMapの拡張メカニズムによれば、容量の値が常に2の累乗になるようにします。次に、負荷係数(loadFactor)の容量(容量)の結果が整数になるようにするには、この数値と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の間である必要があります。数式に従って計算されます。この値はlo​​g(2)で妥当です。
さらに、拡張効率を向上させるために、HashMapの容量には固定要件があります。つまり、2の累乗である必要があります。
したがって、loadFactorが3/4の場合、容量と容量の積は整数になります。
したがって、通常の状況では、特別な理由がない限り、loadFactorの値を変更することはお勧めしません。
たとえば、マップが5 kvしか保存せず、変更されないことが明確にわかっている場合は、loadFactorを指定することを検討できます。
しかし実際には、これはお勧めしません。容量を指定することで、この目標を達成できます。詳細については、HashMapを作成するときに初期容量を設定することを提案しているAlibaba Java開発マニュアルを参照してくださいが、どれくらいが適切ですか?
参考資料:
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/
著者について:ホリスは、コーディングの人々、現在のアリババの技術専門家、パーソナルテクノロジーブロガー、技術記事、数千万のネットワーク全体を読む量、「3クラスプログラマー」の共同著者のためのユニークな探求を持っています。

  • 詳細|その他のすばらしい記事-
    大きなトピックを残すことにしました:なぜ同期できないのか、命令の再配置を禁止しないのに、順序を保証できるのか?
    テクニカルディレクターのアドバイス:なぜこれほど多くのテクノロジーに精通しているのに、プロジェクトを行うのが苦手なのですか?
    Undertowテクノロジー:多くのSpring Boot開発者
    が世界最大のアダルトウェブサイトであるTomcatを放棄し、西洋メディアの最終的な良識を維持する理由

この記事が気に入ったら
QRコードを長押し
オリジナル|コレクションを理解していると言ったのですが、インタビュアーから、HashMapの負荷率が1に設定されていない理由が尋ねられました。 ?
してホリスをフォローしください。友達の輪に転送してください。これが私の最大のサポートです。
良い記事、私は❤️を読んでいます

おすすめ

転載: blog.51cto.com/13626762/2544190