どのようにJava 8のHashMapは、多くのキーが同じハッシュコードを持っている場合、バランスの取れたツリーに縮退しますか?キーがComparable
を実装して順序を定義する必要があることを読みました。HashMapはハッシュとツリーを実装するための自然な順序?Comparable
を実装しないクラス、または複数の相互に比較できないComparable
実装が同じマップ内のキーである場合はどうでしょうか?
HashMapの 実装メモのコメント は、私が書くよりもHashMapの操作をよりよく説明しています。ツリーノードとその順序を理解するための関連部分は次のとおりです。
このマップは通常、ビニングされた(バケット化された)ハッシュテーブルとして機能しますが、ビンが大きくなりすぎると、Java.util.TreeMapと同様に構造化されたTreeNodeのビンに変換されます。 [...] TreeNodesのビンは、他のビンと同様にトラバースして使用できますが、過剰に入力された場合の高速ルックアップもサポートします。 [...]
ツリービン(つまり、要素がすべてTreeNodeであるビン)は、主にhashCodeによって順序付けられますが、タイの場合、2つの要素が同じ「クラスCはComparableを実装する」型である場合、それらのcompareToメソッドが順序付けに使用されます。 (これを検証するためにリフレクションを介して保守的にジェネリック型をチェックします-メソッド comparableClassFor を参照してください)。ツリービンに追加された複雑さは、キーが別個のハッシュを持っているか、または順序付け可能である場合に、最悪の場合のO(log n)操作を提供することに価値があります。配布されているもの、およびそれらが比較可能である限り、多くのキーがhashCodeを共有するものと同じです。 (これらのどちらも当てはまらない場合、予防策を講じない場合と比較して、時間とスペースの約2倍の無駄が生じる可能性があります。しかし、既知のケースは、ユーザープログラミングの慣習が遅いためにあまり遅く、ほとんど影響がないことです。)
2つのオブジェクトが等しいハッシュコードを持っているが相互に比較できない場合、メソッド tieBreakOrder
が呼び出され、最初にgetClass().getName()
(!)の文字列比較によって次に、System.identityHashCode
。
実際のツリー構築は treeifyBin
から始まり、ビンが TREEIFY_THRESHOLD
(現在8)、ハッシュテーブルに少なくとも MIN_TREEIFY_CAPACITY
容量(現在64)。これはほとんど正常な赤黒木実装( crediting CLR )であり、ハッシュビンと同じ方法でトラバーサルをサポートするためにいくつかの複雑化があります(例: removeTreeNode
=)。
code を読みます。ほとんどが red-black tree です。
実際にはComparable
の実装は必要ありませんが、可能であればそれを使用できます(たとえば find method を参照)
HashMap
には、この問題を回避するために、内部のオブジェクトに補足の2ビット長のハッシュを適用する独自のハッシュメソッドがあります。
質の悪いハッシュ関数を防ぐために、与えられたhashCodeに補足ハッシュ関数を適用します。 HashMapは2の累乗の長さのハッシュテーブルを使用するため、これが重要です。それ以外の場合は、下位ビットで差異のないhashCodeの衝突が発生します。注:nullキーは常にハッシュ0にマップされるため、インデックス0になります。
それがどのように行われるかを確認したい場合は、 HashMapクラスのソース の内部を見てください。
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}