HashMapのと原則基礎となる実装(ソースコード解析)

オリジナルリンク: https://blog.csdn.net/qq_41345773/article/details/92066554

注意:JDK1.7に基づいて記事の内容を分析し、1.8は記事の最後に変更を加えることについて説明します。

まず、私たちの共通のHashMapをよく理解して最初の
1、の概要は
、のためので、1つのキーだけの方法HashMapの地図ベースのインターフェイス、記憶素子は、キーと値のペアで、キーは一意でなければなりませんので、ヌル値nullを使用して構築することができますヌル、HashMapのは、他の要素に順序を保証しない、それは無秩序ではなく、順次同様にです。HashMapのは、スレッドセーフです。

2、継承
パブリッククラスのHashMap <K、V>延びクラスAbstractMap <K、V>
    用具地図<K、V>、Cloneableを、直列化
 
。3、基本的なプロパティ

静的最終INT DEFAULT_INITIAL_CAPACITY = 1 << 4 ; // 16のデフォルトの初期サイズ 
静的DEFAULT_LOAD_FACTORフロートファイナル= 0.75F; //負荷係数0.75
静的最後のエントリ[] = {} EMPTY_TABLE <??>; //初期化配列のデフォルト
過渡int型のサイズ;のHashMapの中の要素の//数
int型のしきい値; //は、必要がハッシュマップの容量を調整するかどうかを決定  
注:HashMapの拡張操作は非常に時間のかかる作業ですので、我々は、容量の地図を見積もることができれば、それはすることが最良です複数の拡大を回避するために、デフォルトの初期値は、。HashMapのスレッドが安全ではない、マルチスレッド環境でのConcurrentHashMapをお勧めします。

第二に、違いが頻繁のHashMapのやHashtableの頼ま
1、スレッドの安全性
の両方のを主な違いは、HashMapのは、スレッドセーフではないながら、ハッシュテーブルは、スレッドセーフであるということです。

スレッドの同期を確保するために、synchronizedキーワードに追加されたハッシュテーブルの実装方法、そのため比較的HashMapのパフォーマンスは高くなりますそれ以外の場合は必要な場合を除き、マルチスレッド環境で使用する場合に使用HashMapのは、コレクションを使用する必要がある場合、我々は通常のHashMapを使用することをお勧めします。スレッドセーフなコレクションを取得するためにsynchronizedMap()メソッド。

注意:

Collections.synchronizedMap()の実装原理は、クラスのコレクションSynchronizedMap内で定義されています。このクラスは、スレッドの同期を確保するために、同期メソッド呼び出しを使用して、Mapインタフェースを実装し、または私達はもちろんのインスタンスをHashMapを渡す実際、簡単な動作彼はCollections.synchronizedMap()メソッドは、私たちは、自動的に他のCollections.synchronizedXXの方法と同様のスレッドの同期であり、同様の原理を実装するために操作HashMapの中に同期を追加するために役立ったと述べました。

2は、別のヌルのため
のHashtableをキーとしてnullを許可していない間のHashMapのヌル、キーとして使用することができます
キーとしてHashMapのサポートNULL値けれども、しかし、この提案は、まだそのような使用を避けることであるため、問題が発生した場合に注意して使用し、ない場合ので、調査とそれは非常に面倒でした。
注:キーとしてハッシュマップがnullで、常に最初の配列にノードテーブルに格納されています。

3、継承構造の
HashMapのは、Mapインタフェースの実装で、HashTableには、Mapインターフェースと抽象クラスの辞書を実装しています。

図4は、初期容量の拡張
ハッシュマップ16、11のハッシュテーブルの初期容量の初期容量、双方0.75のフィルファクタがデフォルトです。

電流容量がHashMapのすなわち膨張倍になるとき:容量* 2、ときに二重容量拡張ハッシュテーブルすなわち+1:容量* 2 + 1。

図5に示すように、ハッシュ計算の二つの異なる方法
ハッシュテーブルハッシュ計算ハッシュコードキーが配列表の長さを直接モジュロとして使用されます

key.hashCodeハッシュ= INT();
int型のインデックス=(ハッシュ&0x7FFFFFFFでは)tab.length%、
キーハッシュコードに基づいて計算HashMapのハッシュは、より良い得るとテーブルアレイ長に取るために第2のハッシュ、ハッシュ値を行なっタッチ。

ハッシュハッシュ= INT(key.hashCode());
INT I = indexFor(ハッシュ、table.length)、
 
ハッシュ(HのINT)INT静的{
        //この関数は、唯一異なるハッシュコードによることを確認
        //定数倍で各」ビットを境界位置有し有する
        (約デフォルト負荷率AT 8)//衝突の数を。
        H ^ =(>>> 12はHである)^(H 20 >>>である);
        ^ H(H >>> 7)^(Hを返します4 >>>);
    }
 
 静的INT indexFor(Hはint、int型の長さ){
        リターン&H(1-長さ);
3、ハッシュマップデータ格納構造
1、HashMapのデータ記憶のリンクされたリスト配列が達成される
HashMapの使用エントリキーと値のペアを格納するための配列、キー入力エンティティからなる各ペアは、エントリークラスが実際に次のポインタを有する片方向リンク・リスト構造であり、次のエントリ・エンティティは、競合ハッシュを解決するために、接続されていてもよいです問題。

アレイストレージの間隔が、それは偉大な宇宙複雑で、連続、深刻なメモリフットプリントです。アレイ機能はあるが、アレイバイナリサーチ時間複雑度が小さい、(1)Oである:簡単に挿入し、問題を削除、アドレッシング。

離散リスト記憶部は、空間計算量が非常に小さいので、よりリラックスしたメモリを取るが、O(N)の大規模な時間複雑。機能の一覧は以下のとおりです。簡単に難しく、挿入および欠失に取り組みます。

 

 

 

グラフから、我々は、アレイ+、長さ16の配列からなるリストのデータ構造を参照することができ、各素子は、第1のノードのリンクされたリストに格納されています。次いで、これらの要素は、それが配列に格納されているもの規則に従っています。%レンは、一般的にハッシュすることによって得られる(key.hashCode())、すなわちアレイの重要な要素のハッシュ値は、モジュロ長さを得ました。上記の例では、ハッシュ・テーブル、12、28 = 12%16%16%= 12108 16%= 12140 16 = 12。したがって、12,28,108及び140は、位置の配列インデックス12に格納されています。

静的な内部クラスのエントリを達成するためにあるのHashMap、その重要な属性は、ハッシュ、キー、値、次の。

チェーン・データの概念的な構造を使用してハッシュマップ。私たちは、次のアクションに次のエントリクラスの属性ポイントを持っている上記のエントリです。示さ比喩、最初の鍵ペアAにおいては、計算することにより、そのハッシュキーが取得したインデックス= 0:エントリ[0] = A. しばらくして、今どのように、0に等しいもある指数を算出することで、キーと値のペアBに来ますか?HashMapのは何:B.next = A、エントリー[0] = B、彼らが来る場合C、指数も0に等しい、次いでC.next = B、エントリー[0] = C;我々は見つけることローカルインデックス= 0次のリンクを介して彼らとA、B、C 3キーと値のペアに実際には、アクセスこのプロパティ。だから、疑い心配しないでください。それは、配列に格納されて挿入された最後の要素です。この時点まで、およそHashMapのは、我々が明らかにされている必要があることを認識しています。

 VのPUT公開(キーK、V値){
        IF(キー==ヌル)
            putForNullKey(値)を返す; //ヌル常にリストアレイに最初に配置
        INTハッシュ=ハッシュ(key.hashCode() )。
        indexFor I = INT(ハッシュ、table.length);
        //リストトラバース
        {(E = NULL ;! E = e.nextエントリ<K、V>表E = [I])のための
            オブジェクト・K;
            //もしキーをリストがすでに存在しているに、それが新しい値に置き換えられ
            、{(ハッシュ== &&((K = e.key)== key.equals ||キー(K))e.hash)IF
                V = OLDVALUE e.Valueと、
                E値= .Valueの;
                e.recordAccess(この)
                ; OLDVALUE返す
            }
        }
 
        ModCountを++。
        addEntry(ハッシュ、キー、値、I);
        戻りNULL;
    }
 
 
 
addEntryを(ハッシュINT、Kキー、Vの値は、bucketIndexをINT)無効{
    エントリ<K、V> E =表[bucketIndex];
    表[bucketIndex] =新しい新エントリ<K、V>(ハッシュ 、キー、値、E); // パラメータeは、Entry.nextある
    サイズがしきい値、膨張のテーブルサイズを超えた場合//。再ハッシュ
    (サイズ++> =閾値)IF
            リサイズ(2 * table.length);
}
D.重要な深さ分析方法
1、コンストラクタ
HashMapの()//引数なしでコンストラクタ
のHashMap(INT InitialCapacityの値)//指定された初期容量コンストラクタ 
HashMapの(INT InitialCapacityの値、loadFactorフロート) //が初期容量と負荷率指定
ハッシュマップ(地図<?Kが延び、? // M V延び>) のHashMapに、指定されたセットを
HashMapの構成は4つの方法、施工方法を提供し、第三の方法は、実行依存しているが、最初の三つの方法を使用すると、配列要素の場合に記憶されているコンストラクタテーブル長テーブルを呼び出す場合でも、アレイの動作を開始していないHaspMapまだゼロ。第コールinflateTableコンストラクタ()メソッドで初期化テーブルを完了し、そしてmはハッシュマップに追加された要素です。

図2に示すように、添加する方法
パブリックVのPUT(キーK、V値){
        IF(表== EMPTY_TABLEを){//もし初期
            inflateTable(閾値);
        }
        (キー==ヌル)//番号0の位置に配置されている場合は
            リターンputForNullKey(値);
        int型のハッシュ=ハッシュ)(キー; //のハッシュ値を計算
        INT iは= indexFor(ハッシュ、table.lengthを ); // 入力[]記憶位置を計算する
        (エントリ<K、V>のために E =表[I]、E = NULL ;! E = e.next){
            オブジェクトK;
            IF(ハッシュをe.hash == &&((K = e.key)== key.equals ||キー(K))){
                OLDVALUE e.Value = V;
                e.Value =値;
                e.recordAccess(この);
                OLDVALUEを返します;
            }
        }
 
        ModCount ++;
        addEntry(ハッシュ、キー、値、I); //マップに追加
        戻りNULL;
}
このプロセスでは、まず、初期化されていない場合、テーブルを初期化するかどうかを決意をキーと値のペアを追加する(割り当てられた領域アレイの、エントリー[]の長さ)。次にエントリー[]に入れキー== NULLの、番号0の位置である場合、キーは、NULLであるかどうかを決意。要素が存在するがあれば要素は、位置を有するかどうかを決定する、記憶場所の入力[]アレイを計算する、単一リスト上のエントリ[]配列の場所を横断。キーが既に古い値ポイント値を置き換え、値を持つ新しい価値を存在し、値の古い値を返す場合は、キーがあるかどうかを決定します。キーは、HashMapの中に存在しない場合は、プログラムの実行は下向きに続きます。キーvlaue、エントリ生成エンティティがハッシュマップエントリ[]アレイに加えました。

。3、addEntry()メソッド
/ *
 ハッシュの*ハッシュ値
 *キーキー
 *値値値
 * bucketIndexエントリー[]配列は、インデックス格納
 * / 
(ハッシュINT、鍵K、V値bucketIndexをINT)addEntryをボイド{
     ((IF !サイズ> =閾値)&&(ヌル=表[bucketIndex])){
         リサイズ(2 * table.length); //拡大操作、NEWTABLEへのデータ要素の位置は、先行すると、リストの順序を再計算しました代わりに、
         ハッシュ=(ヌル=キー!):? 0(キー)ハッシュ;
         bucketIndex = indexFor(ハッシュ、table.length);
     }
 
    createEntry(ハッシュ、キー、値、bucketIndex);
}
無効createEntry(ハッシュint型、キーK、値V、INT bucketIndex){
    エントリ<K、V> E =表[bucketIndex];
    表[bucketIndex] =新しい新規エントリ<>(ハッシュ、キー、値、E)。
    サイズが++;
}
、容量を判断する前に添加する、特定の操作に加え、現在の容量がしきい値に達すると、入力[]配列、高度な拡張操作、テーブル長さ2の空間電荷容量に格納する必要がある場合回。ハッシュ値、及び拡張後の拡張前の記憶位置のアレイ、リストの順序と逆の順序リストを再計算します。エントリ新たに追加されたエンティティは、その後、リストの先頭の現在のエントリ[]の位置に格納されます。1.8前に、新たに挿入された要素は、リストの先頭の位置に配置されているが、高度に並行環境では、この作業を容易にデッドロックにつながるので、1.8の後、新たに挿入された要素がリストの最後に配置されます。

4、获取方法:取得
{パブリックVのGET(オブジェクトキー)を
     (キー==ヌル)場合
         //返回テーブル[0]的値は值
         (getForNullKeyを返します)。
     エントリ<K、V> =エントリのgetEntry(キー)。
 
     ヌル==エントリを返しますか?ヌル:entry.getValue();
}
最後のエントリ<K、V>のgetEntry(オブジェクトキー){
     IF(サイズ== 0){
         戻りヌル。
     }
 
     int型のハッシュ=(キー==ヌル)?0:ハッシュ(キー)。
     (;エントリー<K、V> E =表[indexFor(ハッシュ、table.length)]のために
         ; E = NULL!
         {E = e.next)
         オブジェクトK。
         もし(e.hash ==ハッシュ&&
             !((K = e.key)==キー||(キー= nullの&& key.equals(K))))
            リターンE;
      }
     戻りNULL;
}
第1のハッシュ値を算出し、GETメソッドでは、テーブル内の鍵格納場所を取得するindexFor()メソッドを呼び出すと、キーと指定されたキーコンテンツエントリに等しいを見つけるためにリストを、その場所で単一のリンクされたリストを得ることで、 entry.value戻り値。

5、删除方法
パブリックV削除(オブジェクトキー){
     エントリ<K、V> E = removeEntryForKey(キー)。
     リターン(E == nullのヌル:?e.value)。
}
最後のエントリ<K、V> removeEntryForKey(オブジェクトキー){
     IF(サイズ== 0){
         戻りヌル。
     }
     int型のハッシュ=(キー==ヌル)?0:ハッシュ(キー)。
     int型I = indexFor(ハッシュ、table.length)。
     エントリ<K、V> PREV =表[i]は、
     エントリ<K、V> E = PREV。
 
     一方、(!E = NULL){
         エントリ<K、V>次= e.next。
         オブジェクトK;
         IF(e.hash ==ハッシュ&&
             !((K = e.key)==キー||(キー= NULL && key.equals(K)))){
             modCount ++。

             IF(PREV == E)
                 表[I]は=次に、
             他
                 prev.next =次に、
             e.recordRemoval(この)
             ; Eを返す
         }
         PREV = E;
         E =次に、
    }
 
    戻りE;
}
削除操作を、第1の計算キーのハッシュ値を指定し、テーブル内の記憶位置を算出し、リストを横断する、直接的なリターンが存在しない場合、現在のエントリの位置エンティティが存在する場合、現在位置が、エントリ・エンティティが存在するか否かを判断します。エントリは、次の、予め、すなわち、それぞれ、3つの参照を定義します。第1の予備決定され、Eをループの過程で同じである、[I] =次= nullを直接テーブルに、唯一つの要素の場合に等しい、テーブル現在位置することを示します 。事前の形成場合- > E - >次に、Eが基準を失った- >次の接続関係、等しい場合は、事前にそのよう鍵eが、指定されたキーに等しいかどうか判断されます。

。6、のcontainsKey
パブリックブールのcontainsKey(キーオブジェクト){
        のgetEntry(キー)= NULL ;!戻り
    }
最後のエントリ<K、V>のgetEntry(キーオブジェクト){
        ハッシュINT =(キー==ヌル)0:ハッシュ(キー?。ハッシュコード());
        (エントリ<K、V> E =表[indexFor(ハッシュ、table.length)]のために;
             E =ヌル;!
             E = e.next){
            オブジェクトK;
            IF(ハッシュ== e.hash &&
                ((K = e.key)== ||キー(キー= NULL && key.equals(K))!))
                戻りE;
        }
        戻りNULL;
    }
のcontainsKeyハッシュが最初に計算され、ハッシュtable.lengthを使用しています表は、[インデックス]要素は同じキー値が含まれている検索横断、触知できるインデックス値を取ります。

。7、のcontainsValue
パブリックブールのcontainsValue(Object値){
    IF(値== NULL)
            containsNullValueを(返す);
 
    エントリー[]タブ=表は、
        のために(; Iはtab.lengthを<; I = 0 int型Iは++)
            (E =エントリのタブ[I]、E = NULL ;! E = e.next)
                IF(value.equals(e.Value))
                    trueに戻り、
    偽に戻る;
    }
のcontainsValueメソッドは比較的粗いと、値が見つかるまで直接トラバース元素であり、 HashMapのとは、一般的な方法の配列が含まれており、何の違いをリストしないことを基本的にのcontainsValue方法は示して、あなたはそれが少なく、効率的なのcontainsKeyようになる期待しないでください。

五は、JDK 1.8に変更
されたリンクリストアレイ+ +赤黒木の実装を使用して1、HashMapを。
Jdk1.8でのHashMapの実装にいくつかの変更をしたが、基本的な考え方は、配列+リンクリストでなりませんでしたが、いくつかの場所で最適化され、代わりにこれらの変更で、次のを見、データ構造鎖長が閾値を超えた変更リストストレージアレイ+ +赤黒木は、(8)、リストが赤黒木に変換されます。パフォーマンスのさらなる増加。

2、簡単な分析法を置きます:

VのPUT公開(キーK、V値){
    //呼び出しPutVal()メソッド完了
    PutVal(ハッシュ(キー)、キー、falseに、真の値)を返す;
}
 
最終V PutVal(ハッシュINT、鍵K、V値、 onlyIfAbsentブール、
               ブール追い出し){
    ノード<K、V> []タブ、ノード<K、V> P、N-INT、I;
    //テーブルがかどうか初期化、初期化動作または決定
    (=テーブル(タブ)場合== NULLを||(= N-tab.length)== 0)
        N- =)長;.(タブリサイズ(=)
    なし要素、直接割り当てた場合、//インデックス格納された位置を算出する
    ((P =タブなら[I =(N - 1)・ハッシュ])== NULL)。
        タブ[I] newNode(ハッシュ、キー、値、NULL)=;
    他{
        ノード<K、V> E、K Kを;
        //ノードがすでに存在する場合、割り当てを行います
        もし(p.hash ==ハッシュ&&
            ((K = p.key)== ||キー(キー= nullの&& key.equals(K))!))
            E = P;
        //リストが赤黒木であるか否かを判断する
        IF(TreeNodeのP instanceofは)他に
            //赤黒木オブジェクト操作
            E =((ツリーノード<K、V>)P).putTreeVal(本、タブ、ハッシュ、キー、値);
        他{
            //リンクリスト、
            用(INT BinCount = 0 ;; ++ BinCount ){
                ((E = p.next)== NULL){IF
                    newNode =(ハッシュ、キー、値、NULL)p.next;
                    8の//鎖長、赤黒木は、リストに格納されている
                    (binCount> IF = TREEIFY_THRESHOLD - 1)-1 // 1ため。
                        treeifyBin(タブ、ハッシュ);
                    BREAK;
                }
                //キーが上書きされ、存在する
                (ハッシュ== && e.hashのIF
                    !((K = e.key)== ||キー(キー= nullの&& key.equals(K))))
                    BREAKを;
                P = E;
            }
        }
        のためのIF {//既存のキーマッピング(E = NULL!)
            V = OLDVALUE e.Value;
            IF(onlyIfAbsent OLDVALUE == NULL ||!)
                e.Value =値;
            afterNodeAccess(E);
            OLDVALUEを返します;
        }
    }
    //変更の数を記録
    ++ modCountと、
    容量が必要な場合//分析
    IF(サイズ++>閾値)
        リサイズ();
    //動作なし
    afterNodeInsertion(追い出し);
    戻りnullが;
}
返されるヌルが存在しない場合、ノードのキーが存在する場合、古い値を返します。
----------------
免責事項:この記事はCSDNブロガー元の記事「heicy」であり、BY-SAの再現著作権契約、CC 4.0に従って、元のソースのリンクと、この文を添付してください。 。
オリジナルリンクします。https://blog.csdn.net/qq_41345773/article/details/92066554

おすすめ

転載: blog.csdn.net/tiantian1980/article/details/102502964