(転載)HashMapでX%length == X&(length-1)を使用する理由(剰余%および操作と変換の問題)

転載:元のリンクhttps://blog.csdn.net/ricardo18/article/details/108846384
声明:誰かの権利を侵害した場合は、私に連絡してください。削除します。
専門家を歓迎してスプレーしてください。

1つは、問題につながる

HashMapのソースコードの実装を説明するとき、次の点があります。

①初期容量は1 << 4、つまり24 =
ここに画像の説明を挿入
16②負荷率は0.75です。HashMapに格納されている要素の割合が全容量の75%を超える場合は、容量を拡張し、超えない場合は容量を増やしてください。 int型の範囲、2の累乗(元の2倍の長さを参照)の展開を実行し、
ここに画像の説明を挿入
それを2倍にします
ここに画像の説明を挿入
。③新しい要素が追加されると、HashMap内のこの要素の位置が計算されます。この記事の主な文字ハッシュ操作です。これは3つのステップに分かれています。

最初のステップ:hashCode値を取得します:key.hashCode()

ステップ2:高レベルの操作に参加する:h >>> 16

3番目のステップ:モジュロ演算:(n-1)&ハッシュ

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
 
    tab[i = (n - 1) & hash];

ps:コードの6行目は自分で追加したものです。

優れたハッシュアルゴリズムにより、要素の分布がより均一になり、ハッシュの衝突が減少することがわかっています。この領域でのHashMapの処理は非常に巧妙です。

最初のステップは、hashCodeを取得することです。このメソッドは、nativeで装飾されたネイティブメソッドであり、int型の値(メモリアドレスに従って変換された値)を返します。通常、このメソッドを書き直します。

2番目のステップでは、取得したハッシュ値の右側に16ビットの符号がなく、上位ビットは0で埋められます。そして、前のステップで取得したハッシュコードを使用してビット単位のXOR演算を実行します。これの用途は何ですか?これは、ハッシュコードの衝突を減らすために、実際には外乱関数です。右シフトは16ビットで、32ビットのちょうど半分です。元のハッシュコードの上位ビットと下位ビットを混合して下位ビットのランダム性を高めるために、上位半分の領域と下位半分の領域がXORされます。さらに、混合された低レベルの機能にはいくつかの高レベルの機能がドープされているため、高レベルの情報も偽装して保存されます。これは、上位ビットビットと下位ビットビットの両方がハッシュの計算に含まれるようにするためです。

興味のある方は、JDK1.7をご覧ください。実際、JDK1.8では4回の妨害があり、1回だけでした。効率を確保しながら競合を減らすためだと思います。
  ここに画像の説明を挿入
この記事の焦点は、3番目のステップ、前の2つのステップで取得したハッシュ値、およびビット単位のAND演算のHashMapのコレクション長から1を引いたものです:(n-1)&hash。しかし実際には、多くのハッシュアルゴリズムは、要素の分布を均一にするために、モジュロ演算を使用し、値を使用して全長、つまりn%ハッシュを変調します。コンピューター内の&の効率は%よりもはるかに高いことがわかっているので、%を&操作に変換するにはどうすればよいですか?HashMapでは、(n-1)&hashが計算に使用されますが、これはなぜですか?

これは、このブログで理解する質問です。

2.結論

最初に結論を出します。

注長さ= 2n時間、X%長さ= X&(長さ-1)

つまり、長さが2のn乗の場合、モジュロ%演算をビット単位のAND演算に変換できます。

例:9%4 = 1、9はバイナリで1001、4-1 = 3、3はバイナリで0011です。9&3 = 1001&0011 = 0001 = 1

別の例:12%8 = 4、12のバイナリ値は1100、8-1 = 7、7のバイナリ値は0111です。12&7 = 1100&0111 = 0100 = 4

上記の2つの例4と8はどちらも2のn乗であり、結論は正しいです。長さが2のn乗でない場合はどうでしょうか。

例:9%5 = 4、9はバイナリで1001、5-1 = 4、4は0100です。9&4 = 1001&0100 = 0000 = 0。明らかにそれは真実ではありません。

なんでこんな感じ?以下で詳しく分析してみましょう。

3.分析プロセス

まず、次のルールを知っておく必要があります。

①、 "<<"左シフト:右側の空きビットに0を加えると、左ビットがワードの先頭から絞り出され、左シフトに1ビットを掛けることは2を掛けることに相当します。

②、「>>」を右にシフト:右側のビットを絞り出し、右に1ビットシフトした値は2で割った値に相当します。左にシフトアウトされたスペースの場合、正の数の場合は0で埋められ、負の数の場合は、使用するコンピューターシステムに応じて0または1で埋められます。

③、> >>> "符号なし右にシフトし、右のビットを絞り出し、左にシフトアウトしたスペースに0を加算します。

2進数の特徴からすると、誰もがよく理解していると思います。

任意の10進数XnXn-1Xn-2 ... X1X0が与えられた場合、それを2進表現に分解します。

XnXn-1Xn-2…X1X0 = Xn 2n + Xn-1 2n-1 +…+ X1 21 + X0 20 3-1

ここでの10進数は3桁しかありません。同様に、N桁の場合、2の累乗は0からNに順番に増加します。

上記の結論に戻る:長さ= 2nの場合、X%長さ= X&(長さ-1)

除算の場合、配当は分配率を満たします(除数は満たされません)。

確立:(a + b)÷c = a÷c + b÷ c3-2式

正しくない:a÷(b + c)≠a÷c + b÷c

3-1の式と3-2の式から、10進数を2kの数で割ると、10進数を3-1の式の表現に変換できることがわかります。

(XnXn-1Xn-2…X1X0)/ 2k =(Xn 2n + Xn-1 2n-1 +…+ X121 + X0 20)/ 2k = Xn 2n / 2k + Xn-1 2n-1 / 2k +…+ X1 21 / 2k + X0 20 / 2k

上記の式の残りを見つけたい場合は、一目でわかると思います。

①.0<= k <= nの場合、余りはXk 2k + Xk-1 2k-1 +…+ X121 + X0 20、つまりkより大きいn乗の場合、破棄しました(大きいそれらはすべて2kで割り切れます)、そして私たちはすべて取り残されます(kよりも小さいものは2kで割り切れません)。次に、残りは残りです。

②.k> nの場合、余りは10進数全体になります。

これを見て、私たちは結論を証明することに非常に近いです。上記のバイナリシフト操作に戻ると、右にnビットシフトすることは、2nの累乗で除算することを意味します。これから、非常に重要な結論が得られます。

10進数は2nの余りを取ります。この小数を2進数に変換し、2進数をn桁右にシフトできます。削除されたn桁が余りです。

余りを計算する方法を知っているので、nの数を削除するにはどうすればよいですか?

次のようにバイナリで20、21、22 ... 2nを見てみましょう。

0001、0010、0100、1000、10000…

上記の数を1つ減らします。

0000、0001、0011、0111、01111…

AND演算子&の規則によれば、ビットがすべて1の場合、結果は1になり、それ以外の場合は0になります。したがって、任意の2進数が2kの余りを取る場合、残りが保持されている場合でも、この2進数と(2k-1)の間でビット単位のAND演算を実行できます。

これは、前に示した結論を完全に証明しています。

注長さ= 2n時間、X%長さ= X&(長さ-1)

上記の式を満たすには2nでなければならないことに注意してください。そうでない場合は間違っています。

総括する

上記の分析プロセスを通じて、式の正しさを完全に証明しました。HashMapの実装プロセスに戻ると、HashMapの初期容量が1 << 4であり、各拡張が2倍になる理由がわかります。ハッシュアルゴリズムは完全に満たされなければならないからです。

おすすめ

転載: blog.csdn.net/qq_45531729/article/details/112370306