私たちは、「章の中でHashMapの原理(a)の概念と基本的なアーキテクチャは、」HashMapのと容量の容量、しきい値変数とloadFactor変数を含む一般的な概念や変数に格納されたデータ構造を説明しました。この章では、膨張機構とのHashMapへのアクセスの原理を説明します。
基本的な概念を初めて目:
変数テーブル:ハッシュマップデータ構造根底には、キーと値のペアを格納するためのノードクラスのエンティティの配列です。
容量:それはメンバ変数ではありませんが、概念を知るために必要不可欠である、容量を示します。
可変サイズは: HashMapの格納されたキー値ペアの数を示します。
loadFactor変数:完全な範囲を測定するために使用される負荷率、;
可変閾値:閾値、この値を超えた場合、テーブルには拡張を示すことを示します。
A. Putメソッド
HashMapのは、アレイ位置に記憶されたハッシュアルゴリズムを使用して得られ、次いで、メソッド呼び出しは、変数テーブルに記憶されたキーと値のペアを置きます。ストアドプロシージャについてマップを見てみましょう。
簡単な説明:
ハッシュ(オブジェクトキー)アルゴリズムによって得られた1)のハッシュ値。
2)サイズ変更の実装は、()の拡張または長さのためにnullである場合、テーブル0かどうかを決定します。
3)配列表[I]を決定する配列とインサートテーブルアレイインデックスiの長さ、により得られたハッシュ値が空であるか、NULLである;
4)テーブル[I] == NULLは、直接的に)8ステアリング、新しいノードを追加する場合は、場合にテーブル[i]は、ステアリング5空ではない);
5)表か否かを判断する[I]第一要素及びキーの同一の直接カバレッジ値、同じことがここで参照している場合のようにハッシュコードと等しく、それ以外は6に行く);
6 )分析テーブル[i]のツリーノード、すなわち、テーブルか[i]は赤黒木か、ツリーが鍵ペアに直接挿入されている赤黒木は、そうでなければ7に向ける場合);
7)テーブルを横断[i]を、鎖長が、8より大きく8よりも大きいか否かを判定し、リストがそうでなければ、リスト赤黒木、インサート内赤黒木、挿入操作に変換され、直接カバレッジ値に見つかった場合トラバーサルキーが既に存在し;
8)成功した挿入後、拡張のために、超えた場合、最大容量しきい値よりもより上かどうか既存の鍵サイズの実際の数を決定します。
私たちはそこに最も重要な3つの方法を見て、ハッシュ()、putVal()、サイズを変更します()。
1.ハッシュ法
我々は、ソースコードを見て、配列に格納された位置を得るために、ハッシュ法による指数計算します
静的最終int型のハッシュ(オブジェクトキー){ int型H; リターン(キー==ヌル)?0:(H = key.hashCode())^(H >>> 16)。 }
私たちは、ハッシュアルゴリズムのHashMapを取得するには、そのハッシュコードのXOR演算のキー16ハッシュコード値を経て得られた正しい値であることがわかり、なぜちょうど(key.hashCodeを使用していない)、および排他的論理和演算することができますか?私たちは、目的は、ハッシュインデックスを得ることであることを知っている、とハッシュ衝突が可能であり、それは同じハッシュ値を取得するための別のキーがあるので、上記の採用で、その後、起きてからこれを削減する方法を、簡単に衝突産業でありますXOR演算の下位16ビットのハッシュコードのハッシュコード付きハッシュ(オブジェクトキー)アルゴリズムは、得られた高および低最終ハッシュ値の混合物が、衝突の確率がはるかに小さいです。例えば:
スチーマー第1層は、豚肉、牛肉BUN、ニワトリパッケージ、第2のパッケージ層キャベツ、Dousha第三の層のパッケージであり、そこである、第四層は、キノコのパッケージです。そして、あなたは3、外観は解決できないので、2あれば、そこにパッケージを取得するには豚肉の確率は、第三である、ちょうど私にパンを与え、豚肉のパッケージに加えて、第一の層を指して、言った、朝食を買いに来ます、一緒層と混合した4階建てで、その後、豚肉のパッケージを取得する確率ははるかに小さいです。
我々のハッシュ(オブジェクトキー)アルゴリズムの原理は、高および低ミキシング情報の最終的なハッシュ値、それ以上のドーピング元素、最終ハッシュ値のより大きなランダム、およびHashMapのテーブルは、最終的なハッシュ・インデックスに依存します値table.length() - 操作はピック&バンズ、はるかに少ない天然の衝突の処理と同様である&演算子、1。それは次のように計算されます。
hashCodeの始まり:1,111,111,111,111,111 0,100,110,000,001,010
ハッシュコードの16ビット右シフト:0000 0000 0000 0000 1111 1111 1111 1111
XORハッシュ値計算:1,111,111,111,111,111 1,011,001,111,110,101
2. putVal方法
putValキーと値のペアによって伝送する方法は、配列表に追加されます。
/ ** *のImplements map.putと関連した方法は、 * キー用* @paramハッシュハッシュ キーで* @paramキー 値で* @param値を入れて * @param onlyIfAbsent trueに、既存の値を変更しない場合 * @paramの追い出しをテーブル作成モードになって、場合はFALSEに。 * @return前の値がない場合はnull * / 最終V PutVal {(ハッシュ、鍵K、V値、onlyIfAbsentブール、ブール追い出しINT) ノード<K、V> []タブ;ノード<K、V> P、N-INT、I; / ** *現在のテーブルが未定義かアレイHashMapの場合、まだ、その長さを初期化し、サイズ変更によって膨張する第一()、 *膨張の配列を返しますN-の長さ * / IF((タブ=表)== NULL ||(= N-tab.length)== 0) N- =長さ(タブサイズ変更を()=); この位置が要素でない場合//挿入新しいノードに直接、対応&アレイ添字を得るためにビット単位のAND演算により新たな要素を配列の長さのハッシュ値を行う 場合((P =タブ[iが =(N - 1)・ハッシュ])== NULL) タブ[I]はnewNode(ハッシュ、キー、値、ヌル)=; //そうでない場合は、要素の位置を有し、我々はいくつかの追加オペレーション必要 他{ ノード<K、V> E、KをKは; //キーが挿入され、元のキーと同じである場合、ビンをクリックが置き換えられ た場合(p.hash ==ハッシュ&&(( K = p.key)==キー||(キー= NULL &&キー! 等号(K)))) E = P; / ** *キーまたは異なるが、実行putTreeVal新しい要素が挿入された場合、現在のノードは、ツリーノードであるか否かを判断する 赤黒木に*。 * / 他IF(ツリーノードPのinstanceof) E =((ツリーノード<K、V>)P).putTreeVal(本、タブ、ハッシュ、キー、値); //ならないツリーノード、リンクされたリスト・トラバーサルが実行されます 他{ {(INT BinCount = 0 ;; BinCount ++)のために / ** *リスト内の最後のノードと同じ要素の後に見出されない、以下の操作が直接行われ、新しいノードを挿入する 条件*が、赤色に変換することができブラックツリー * / IF((E = p.next)== NULL){ //新しい直接ノード p.next newNode =(ハッシュ、キー、値、NULL); / ** * = TREEIFY_THRESHOLD 8、なぜならbinCountゼロから、すなわち、リストが長く8(両端を含む)より大きい場合、 *赤黒木に。 * / IF(BinCount> = TREEIFY_THRESHOLD - 1。)のため-1 // 1 treeifyBin(タブ、ハッシュ); BREAK; } / ** 同じキー値が最後のノードの前にリストに見つかった場合*(上記の判断と競合しない、直接配列上にある インデックスが同じキー値か否かを判断する*)、次いで置換 * / IF(ハッシュ== e.hash && ((K = e.key)== ||キー(キー= NULL && key.equals(K))!)) BREAK; ; P = E } } 既存のマッピングのためのIF(E = NULL){//!キー V = OLDVALUE e.Value; // onlyIfAbsentがtrueの場合:ポジションはすでに要素を覆って存在していないとき (onlyIfAbsent OLDVALUE == nullの||!)場合は e.Value =値; afterNodeAccess(E); OLDVALUEを返します; } } ++ modCount。 //最後に、閾値決意、膨張場合。 (サイズ++>閾値)IF ;リサイズ() afterNodeInsertion(追い出し); 戻りNULL; }
3.サイズ変更方法
リサイズHashMapの()メソッド、ルール2の電源の容量によって拡張のため
/ ** *ダブルス表やサイズを初期化しますザnullの場合、に割り当て フィールドの目標閾値に開催して*初期容量のアコード。 *それ以外の場合は、電源を使用してWEは、拡張の-TWOであるため、 各ビンから* ElementsはどちらかMUST滞在ATインデックス同じ、または移動 。二つの新しい新規のオフセット表と*のパワー * *表@return * / 最終ノード<K、V> []リサイズ(){ ノード<K、V> [] = oldTab表。 ?OLDCAP = INT(oldTab == NULL)0:oldTab.length; INT oldThr =閾値; INT newCap、newThr = 0; 0より大きい//以前の容量、つまり、新しいオブジェクトが既にハッシュマップ要素である、または初期容量を設定 IF(OLDCAP> 0){ 容量が30 << 1の以前の最大容量限界よりも大きい場合//、臨界値は2 ^ 31-1整数の最大値に設定されています IF(OLDCAP> = MAXIMUM_CAPACITY){ = Integer.MAX_VALUEのしきい値; oldTabを返す; } / ** *以前の2倍量であれば最大容量限界未満ながらデフォルトの容量より大きいかまたは16に等しい、閾値が予め設定された閾値2 閾値ので*回= loadFactor *容量、容量は、loadFactor変わらず、二回拡大 *しきい値が自然に二回拡大しました。 * / 他IF((newCap = OLDCAP << 1)<MAXIMUM_CAPACITY && OLDCAP> = DEFAULT_INITIAL_CAPACITY) 。newThr = oldThr << 1; //ダブルしきい値 } / ** HashMapのハッシュで*コンストラクタ(INT InitialCapacityの値、loadFactorフロート)にそこコード、this.thresholdです * = tableSizeFor(InitialCapacityの値)は、コンストラクタを呼び出すときに、デフォルトは一時の初期容量に割り当てられていることを示し *しきい値しきい値は、ので、ここで新しい容量の初期容量に割り当てられた最後のと同じです。どのような状況下では、文の実装だろうか?呼び出されたとき とき*のHashMap(int型InitialCapacityの値)コンストラクタ、無添加元素 * / 他のIF(oldThr> 0) newCap = oldThr; / ** *デフォルトコンストラクタを呼び出し、初期容量が提供されていない場合、デフォルトの容量DEFAULT_INITIAL_CAPACITYので、 (16)、臨界値は * 0.75 * 16 * / 他{ ; newCap = DEFAULT_INITIAL_CAPACITY ; newThr =(INT)(* DEFAULT_INITIAL_CAPACITY DEFAULT_LOAD_FACTOR) } 上記第二のように、//それが0でないことを保証するために臨界値に判断を下しますnewThr計算しない場合(oldThr> 0)、及び IFを(newThr == 0){ フロートフィート=(FLOAT)newCap * loadFactor;。 。newThr =(newCap <&& MAXIMUM_CAPACITYフィート<(フロート)MAXIMUM_CAPACITY? (INT)フィート:Integer.MAX_VALUEの); } 閾値= newThr; @SuppressWarnings({ "rawtypes" 、 "未チェック"}) / **新しいテーブルを構築し、データテーブルの初期化* / ノード<K、V> [] = NEWTAB(ノード<K、V> [])ノードの新しい新[newCap]; 新たに作成されたテーブルに割り当てられ//新しいテーブル 、テーブル= NEWTAB; IFは(!oldTab = NULL){ //テーブル内の元のデータを横断する新しいテーブルが拡張配置する ための(int型J =を0; J <OLDCAP; J ++){ ノード<K、V> E; !IF((E = oldTab [J])= NULL){ oldTab [J] = NULL; 無し直接放電と//ノードノードリスト添字で新しいテーブル[e.hash&(newCapは- 1)]に位置決めする (e.next == NULL)IF NEWTAB [e.hash&(newCap - 1)] = E; //もしTREENODEノード、NEWTABでツリーにノード 他(ツリーノードEのinstanceof)IF E)((ツリーノード<K、V>)。スプリット(これ、NEWTAB、J、 oldCap)。 //ノードリストの場合、eはリストを横断された電子は、そこにあります、 ことを確実にするために他{//ため 、ノード<K、V> = NULL loHead、loTail =ヌル ノード<K、V> = NULL hiHead、hiTail = NULL; ノード<K、V>次に、 DO { 次ノードレコード// = e.next次; / ** * NEWTAB倍容量古いテーブルの容量の前に、アレイが徐々にに従って増加円形のテーブルインデックスはないので *が、(table.length-1)〜&ハッシュ計算したがって、展開後、上の格納位置 *に変更することができるので、どのような変更は、それは以下のアルゴリズムにより得られる、最終的に発生する。 * * e.hash&oldCapによって再び通ってノードの位置を決定しますハッシュアルゴリズムの後に、それが変更されます、など 1は、彼らが変更と述べた場合* 0の果物は、変更が起こらないことを示しています。:最後に、例えば、どのように理解されて * = 13バイナリe.hashである:0000 1101 * = 32バイナリOLDCAP:0001 0000 *&計算:バイナリ0:0000 0000 loTail = E; } / ** *結論:エレメントは膨張後の位置を変更しない * / IF((e.hash&OLDCAP)== 0){ IF(loTail == NULL) loHead = E; 他 loTail.next = E; * e.hashバイナリ= 18:0001 0010 * OLDCAP = 32バイナリ:0001 0000 *&演算子:32バイナリ:0001 0000 *結論:要素の位置を変更する方法その後、拡張後の変更のだろうか? * NewCap = 64バイナリ:0010 0000 (newCap-1)で*&ハッシュ * 00011111&00010010すなわち有する0010,32 + 2 = 34が0001である * / 他{ IF(hiTail == NULL) hiHead = E; 他 hiTail.next = E; hiTail = E。 } }ながら((E = NEXT)= NULL! ); IF(loTail = NULL){! loTail.next = NULL; / ** *(e.hash&oldCap)== 0、添字変わらない場合、拡張要素の添字に元のテーブルと同じテーブル *添字位置 * / NEWTAB [J] = loHead; } IF(!hiTail = NULL){ hiTail.next = NULL; / ** !* IF(e.hash&oldCap)= 0、対象要素テーブル拡張に原稿台 * [索引+増大膨張量]位置 * / NEWTAB [J + OLDCAP] = hiHead。 } } } } } 戻りNEWTAB。 }
二つ。メソッドを取得します。
私たちは、簡単で、キーを渡すことで、GET(オブジェクトキー)の流れ、()ハッシュアルゴリズムによるハッシュ値の話を(N - 1)配列インデックスの値はノードに対応する場合&ハッシュ、配列の添字を検索するだけでそれが返されるように、キーは、そうでない場合はそうである場合、ノードを見つけるためにリストをトラバースしていない場合、対応するノードを見つけるために、赤黒木をトラバースする、それはtreenNodeであるかどうかを確認するために、次のノードを見つけるnode.next見つけます。私たちは、ソースコードを見て
Vは、{パブリック(オブジェクトキー)を取得し たノード<K、V> E; //ノードを見つけるためにハッシュ(キー)によって得られた第1のハッシュ値は、getNodeを呼び出す(キーハッシュ)を見つける リターン(E = getNode(ハッシュ(キー)、キー?))== nullのヌル:e.Value; } / ** *実装し、関連した方法をMap.get * * @paramハッシュキーのハッシュ キーで* @paramキー なしのIFノードで* @return、またはnull / * ノードファイナル<K、V> getNode(ハッシュINT、キーオブジェクト){ ノード<K、V> []タブ、ノード<K、V>まず、E; N-INT; K K; //により(N - 1) &ハッシュの配列に対応する第一の位置にあるノードを見つける !IF((タブ=表)= NULL &&(= N-tab.length)> 0 && (最初のタブ= [(N - 1)&ハッシュ])= NULL! ){ //ノードは、キー値とちょうど同じである場合、直接返さ (ハッシュをfirst.hash IF == && ((K = first.key) ==キー||(キー!= nullを &&key.equals(K)))) まず返す; //同一ではない場合、ダウン再び見つけること !IF((E = first.next)= NULL){ //ツリーノードは、対応するノードを見つける赤黒木をトラバースした場合 IF(第一のTreeNodeのinstanceof) リターン((TreeNodeの<K、V>)ファースト).getTreeNode(ハッシュ、キー); //リストの場合、対応するノードを見つけるために、リストを横断 {行う (== &&ハッシュをe.hashのIF (( e.key = K)||キー==(= NULL && key.equalsキー(K!)))) リターンE; !}ながら((E = e.next)= NULL); } } 戻りNULL; }
これは、多くの一般的な方法のHashMapのがあるが、いくつかの方法の中核であるが、一般的にこれらの方法及び関連する方法、または同様のロジックを実装し、言うことよりはありません。
III。概要
基本的な概念と、基礎となる構造上の前の章に基づいて、膨張機構の観点から、ソースとアクセスの原理を説明するために、プットを分析し、メソッドを取得し、コアは方法ハッシュ()、putVal()を入れて、リサイズ(getNodeためのgetメソッドの)、コア()、不完全な場合には、批判を喜ばと一緒に進歩を遂げることを望む、ありがとうございました!