同時に、HashCode()と()メソッドをうまく組み合わせることができます。 HashCode()は等しくありません。また、equals()が等しい()等しい()が等しい場合もあれば、等しい場合もあれば、それは異なるに違いありません。
ハッシュマップが取得されると、最初にハッシュコードを比較し、次にequalsを比較すると、ハッシュコード== && equalsが両方であるため、同じキーと見なされます。
1。Hashmapの概要:
HashMapは、ハッシュテーブルに基づいたMAPインターフェイスの非同期実装です。この実装は、すべてのオプションのマッピング操作を提供し、null値とnullキーを使用できます。このクラスは、マッピングの順序を保証するものではありません。特に、順序が続くことを保証するものではありません。
2。Hashmapのデータ構造:
Javaプログラミング言語では、2つの基本構造があり、1つは配列であり、もう1つはこれら2つの基本構造を使用して構築できます。 HashMapは、実際には「リンクリストハッシュ」データ構造、つまり配列とリンクリストの組み合わせです。
上記の図からわかるように、Hashmapの基礎となる層は配列構造であり、配列内の各アイテムは別のリンクリストです。新しいハッシュマップが作成されると、配列が初期化されます。
ソースコードは次のとおりです。
/ ** *必要に応じて、常に2つのパワーでなければなりません。 v値;
エントリは、各マップの要素であることがわかります。エントリーは、リンクされたリストを構成する次の要素への参照を保持しています。
3。Hashmapアクセス実装:
1)ストレージ:
public v put(k key、v value){// hashmapは、nullキーとnull値を許可します。 //キーがnullの場合、putfornullkeyメソッドを呼び出し、アレイの最初の位置に値を配置します。 if(key == null)return putfornullkey(value); int hash = hash(key.hashcode()); int i = indexfor(hash、table.length); // iインデックスのエントリがnullでない場合、ループを介してe要素の次の要素を追跡し続けます。 for(entry <k、v> e = table [i]; e!= null; e = e.next){object k; || equals(k))これがまだエントリがないこと。 modcount ++; // iインデックスにキーと値を追加します。 addentry(key、value、i);
上記のソースコードから、要素をハッシュマップに配置すると、最初にキーのハッシュコードに基づいてハッシュ値を再計算し、ハッシュ値に従って、アレイのこの要素の位置を再計算することがわかります。 )、配列が位置にすでに保存されている他の要素がある場合、この位置の要素はリンクされたリストの形で保存され、新しく追加されたリストはチェーンの頭に配置され、最初の追加が追加されます。チェーンの端に配置されます。配列内のこの位置に要素がない場合は、この位置に[アレイ]のこの位置に直接要素を置きます。
Addentry(ハッシュ、キー、値、i)メソッドは、計算されたハッシュ値に基づいて、配列テーブルのIインデックスにキー値ペアを配置します。 Addentryは、HashMapによって提供されるパッケージアクセス許可の方法です。コードは次のとおりです。
void addentry(int hash、k key、v value、int bucketindex){//指定されたbucketindexインデックスエントリ<k、v> e = table [bucketindex]でエントリを取得します。インデックスと元のエントリテーブル[BucketIndex] =新しいエントリ<K、v>(hash、key、value、e) (size ++> =しきい値)//テーブルオブジェクトの長さを元の2倍に拡張します。 sezize(2 * table.length);
システムがハッシュマップにキー値ペアを保存することを決定した場合、エントリの値はまったく考慮されず、キーに基づいて各エントリのストレージ位置を計算して決定します。マップコレクションの値をキーの保存場所を決定すると、値を保存することができます。
ハッシュ(int H)メソッドは、キーのハッシュコードに基づいてハッシュを一度再計算します。このアルゴリズムは、低ビットが変更されていない場合、および高ビットが変化するときに発生するハッシュ競合を防ぐために、ハイビット計算を追加します。
static int hash(int h){h ^ =(h >>>(h >>> 12);
ハッシュマップで要素を見つけるには、キーのハッシュ値に基づいて配列内の対応する位置を見つける必要があることがわかります。この位置を計算する方法は、ハッシュアルゴリズムです。前述のように、ハッシュマップのデータ構造は配列とリンクリストの組み合わせであるため、もちろん、このハッシュマップの要素の位置が可能な限り均等に分散することを願っています。 1つでは、この位置を使用しているときにハッシュアルゴリズムを見つけると、対応する位置の要素が私たちが望むものであり、リンクされたリストをトラバースする必要がなくなります。クエリの効率。
任意のオブジェクトの場合、HashCode()戻り値が同じである限り、Hash(int H)メソッドを呼び出すプログラムによって計算されるハッシュコード値は常に同じです。私たちが最初に考えることは、要素の分布が比較的均一になるように、アレイの長さのハッシュ値をモジュロすることです。ただし、「Modulo」操作は多くを消費し、これはHashmap:Indexfor(int h、int length)メソッドを呼び出して、テーブル配列にオブジェクトを保存する必要があるインデックスを計算します。
indexfor(int h、int length)メソッドのコードは次のとおりです。
static ind indexfor(int h、int length){return h&(length-1);
この方法は非常に賢いです。それはH&(Table.length -1)を介してオブジェクトの保存されたビットを取得します。速度の条件。 HashMapコンストラクターには、次のコードがあります。
int容量= 1;
このコードは、ハッシュマップの容量が常に初期化中に2のn電源にあることを保証します。つまり、基礎となる配列の長さは常に2のn電源にあります。
長さが常に2のn電力になる場合、H&(長さ1)動作は弾性の長さ、つまりh%長さに相当しますが、%よりも効率が高くなります。
これは非常にシンプルに見えますが、実際には非常に神秘的です。
アレイの長さが15と16であり、最適化されたハッシュコードがそれぞれ8と9であると仮定すると、&操作の後の結果は次のとおりです。
h&(table.length-1)hash table.length-1
8&(15-1):0100&1110 = 0100
9&(15-1):0101&1110 = 0100
-------------------------------------------------------------- -------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------- -------------------------------------------------------------- ------ -------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------- --------------------------------------------
8&(16-1):0100&1111 = 0100
9&(16-1):0101&1111 = 0101
上記の例からわかるように:それらが15-1(1110)で「as」である場合、同じ結果が生成されます。つまり、アレイの同じ位置に配置され、衝突が生成されます。また、リンクされたリストを作成するときに、9がアレイ内の同じ位置に配置されます。リンクリストをトラバースして8または9を取得する必要があります。これにより、クエリの効率が低下します。同時に、アレイの長さが15の場合、ハッシュ値は15-1(1110)で「AS」になると、最後のビットは常に0、0001、0011、0101、1001になります。 、1011、0111、0111、1101は要素を保存することはできません。この場合、アレイが使用できる位置はアレイの長さよりもはるかに小さいということです。衝突の可能性はさらに増加し、クエリ効率が遅くなります!アレイの長さが16の場合、つまり2のn電源である場合、2N-1で取得されたバイナリ数の各ビットの値の値は1です。これにより、元のハッシュの合計のビットが低くなります低いビットであるため、取得した合計ハッシュの低いビットが同じであるように、ハッシュ(int h)メソッドと相まってキーのハッシュコードをさらに最適化し、高ビット計算を追加するため、2つの値のみが2つの値のみを追加します。同様のハッシュ値は、配列内の同じ位置に配置され、リンクリストを形成します。
したがって、アレイの長さが2のnパワーである場合、異なるキーが同じインデックスを計算できる確率が小さくなるため、データは配列に均等に分散されます。つまり、衝突の確率は小さく、相対的で、今回は、特定の場所でリンクリストを通過する必要がないため、クエリの効率が高くなります。
上記のPUTメソッドのソースコードによれば、プログラムがキー値ペアをハッシュマップに入れようとするとき、プログラムは最初にキーのハッシュコード()返品値に基づいてエントリのストレージ位置を決定します。 2つのエントリのキーHashCode()の戻り値は同じであり、それらのストレージの場所は同じです。これら2つのエントリのキーがEquals比較を通じてtrueを返す場合、新しく追加されたエントリの値はコレクションの元のエントリの値をオーバーライドしますが、キーはオーバーライドしません。これら2つのエントリのキーがEquals比較を介してFalseを返す場合、新しく追加されたエントリはコレクションに元のエントリを備えたエントリチェーンを形成し、新しく追加されたエントリはエントリチェーンのヘッドにあります - 詳細は継続されますAddentry()メソッドの説明。
2)読む:
public v get(object key){key == null)return getfornullkey()hash(key.hashcode()); 。 e.valueを返します。}
上記のハッシュアルゴリズムが基礎として保存されているため、このコードを理解するのは簡単です。上記のソースコードから、HashMapで要素を取得するときに、最初にキーのハッシュコードを計算し、配列の対応する位置に要素を見つけてから、対応する位置のリンクリストに必要な要素を見つけることがわかります。キーの等式方法を介して。
3)要約すると、HashMapプロセス全体として全体として全体として、この全体がエントリオブジェクトです。基礎となるハッシュマップは、エントリ[]配列を使用して、すべてのキー価値ペアを保存する必要がある場合、ハッシュアルゴリズムに基づいて配列内のストレージの場所を決定し、に基づいて配列の場所を決定します。等しい方法は、エントリを取得する必要があります。
4。Hashmap'sResize(Rehash):
ハッシュマップにはますます多くの要素があるため、アレイの長さが固定されているため、ハッシュ競合の可能性はますます高くなります。したがって、クエリの効率を向上させるには、アレイサイズの拡張操作も拡張する必要があります。表示:元の配列内のデータは、再計算され、サイズが変更されている新しい配列に配置する必要があります。
では、Hashmapはいつ拡張されますか? Hashmapの要素の数が配列サイズ *LoadFactorを超えると、配列のデフォルト値は0.75です。つまり、デフォルトでは、配列のサイズは16です。次に、ハッシュマップの要素の数が16*0.75 = 12を超えると、配列のサイズが2*16 = 32、つまりサイズの2倍に拡張されます。そして、それを再計算します。アレイ内の各要素の位置は、非常にパフォーマンスを消費する操作です。 。
5。ハッシュマップパフォーマンスパラメーター:
HashMapには、次のコンストラクターが含まれています。
Hashmap():初期容量が16、負荷係数0.75でHashMapを構築します。
Hashmap(int initial -capacity):初期容量と0.75の負荷係数でハッシュマップを構築します。
Hashmap(int initial -capacity、float loadfactor):指定された初期容量と指定された負荷係数を備えたハッシュマップを作成します。
Hashmapの基本コンストラクターであるHashmap(int initial -capacity、float loadfactor)には2つのパラメーターがあり、これらは初期容量の初期容量と負荷係数荷重ファクターです。
初期性:ハッシュマップの最大容量、つまり基礎となる配列の長さ。
LoadFactor:Load Factor LoadFactorは、ハッシュテーブル(N)/ハッシュテーブルの容量(M)の実際の要素の数として定義されます。
負荷係数は、ハッシュテーブルスペースの使用度を測定します。リンクされたリスト方法を使用して、要素を見つける平均時間はO(1+A)です荷重係数は小さすぎる場合、ハッシュテーブルのデータはあまりにもまばらであり、空間の深刻な無駄を引き起こします。
ハッシュマップの実装では、ハッシュマップの最大容量はしきい値フィールドによって決まります。
コードコピーは次のとおりです。
しきい値=(int)(容量 * loadfactor);
負荷係数の定義式に基づいて、しきい値は、この数がこれを超えている場合、実際の負荷係数を削減するためにサイズを変更します。デフォルトの負荷係数0.75は、空間効率と時間的効率のバランスの取れた選択肢です。容量がこの最大容量を超える場合、サイズ変更されたハッシュマップ容量は容量の2倍になります。
if(size ++> =しきい値)sezize(2 * table.length);
6。フェイルファーストメカニズム:
java.util.hashmapはスレッドセーフではないことがわかっているため、他のスレッドがイテレーターを使用するプロセス中にマップを変更すると、scurrentmodificationexceptionがスローされます。
この戦略は、名前が示すように、ModCountフィールドを介して実装されています。 IteratorのspedctionModCount。
hashiterator(){expectsModCount = modcount; ;
反復プロセス中に、modcountとspedctionModCountが等しくない場合、他のスレッドがマップを変更したことを意味します。
ModCountは揮発性と宣言されており、スレッド間の変更の可視性を確保することに注意してください。
最終エントリ<k、v> nextentry(){if(modcount!= expectsModCount)Throw new concurrentModificationecception();
HashmapのAPIでは、次のように述べられています。
すべてのハッシュマップクラスの「コレクションビューメソッド」によって返されるイテレーターは、マッピングが構造から変更されている場合、反復因子の削除方法を渡さない限り、繰り返し作成します変更が行われた場合は、concurrentModificationExceptionを投げます。したがって、同時の変更に直面して、将来の不確実な時期に任意の不確実な行動を危険にさらすことなく、反復因子はすぐに完全に失敗します。
非同期的な修正がある場合、イテレーターの速い故障動作は保証できないことに注意してください。 Fast Fail Iteratorが最適に機能する場合、ConcurrentModificationExceptionをスローします。したがって、この例外に依存するプログラムを作成するのは間違っており、正しい方法は次のとおりです。イテレーターの高速障害動作は、プログラムエラーの検出にのみ使用する必要があります。