HashMap関連の面接の質問

HashMapの実現原理

HashMapの最下層は、配列+リンクリストによって実現されます。Entry配列はキーと値のペアを格納し、各キーと値のペアはEntryエンティティです。Entryクラスは、実際には単一リンクリストです。jdk1.8では、リンクリストの長さが8より大きい場合、リンクリストは赤黒木に変換されます。
ここに画像の説明を挿入

HashMapでの配列とリンクリストの役割

  1. 配列はバケットの位置を決定するために使用されます。バケットの位置を取得するための計算方法は、要素のキーのハッシュ値によって配列の長さをモジュロすることです。
  2. リンクリストは、ハッシュの競合の問題を解決します。ハッシュ値が同じ場合、配列上にリンクリストが形成され、ヘッダー補間法のみが使用されます。ヘッド挿入方法とは?入ってくるものは直接頭に挿入されます。どうして?創業者は、後で来る人は訪問される可能性が高いと感じているからです。ここに画像の説明を挿入

ハッシュ競合の解決策

  1. オープンアドレス法
  2. チェーンアドレス
  3. 再ハッシュ
  4. 波及効果ゾーン法

配列の代わりにリンクリストを使用できますか?

はい。ただし、ハッシュ値を取得する場合は、配列を介してバケットの場所を特定する方が高速です。

HashMap拡張条件

ハッシュの衝突をできるだけ回避するために、バケットがを超える場合はload factor * current capacity,サイズを変更
load fator = 0.75します。
current capacity現在の配列サイズですか

拡張が2の累乗である理由

  1. 効率的に保存および保存するために、HashMapは衝突をできるだけ少なくする必要があります。データをできるだけ均等に分散することです。各リンクリストの長さはほぼ同じです。この実装は、リンクリストのアルゴリズムに基づいています。はデータに格納されます。このアルゴリズムは実際にはモジュロ,hash%lengthです。
    ただし、この操作はシフト操作ほど高速ではありません。
    したがって、ソースコードは最適化されていhash&(length-1)ます。
    つまりhash%length==hash&(length-1)
    、音量が2のn乗であるという保証は(length-1)、誰もそれを実行できるようにすること&1です。
    ここに画像の説明を挿入

HashMapput要素プロセス

  1. キーのhashCode()は、ハッシュ値を取得するために外乱関数によって処理され、現在の要素が格納されている場所は(n-1)&hashによって決定されます。
  2. 衝突がない場合は、直接バケットに入れます
  3. 衝突が発生した場合、要素と格納する要素のハッシュ値とキーが同じであるかどうかを判断します。それらが同じである場合は、値を更新して、キーの一意性を確認します。それらが異なる場合は、zipperメソッドを使用して競合を解決します。リンクリストの形式でバケットに保存した後。
  4. リンクリストに8つの要素がある場合、赤と黒に変わります。
  5. バケットがいっぱいの場合はサイズを変更します

HashMapは要素プロセスを取得します

  1. キーのhashCode()に従ってハッシュ操作を実行し、インデックスを計算します
  2. バケットの最初のノードがヒットした場合は戻ります。競合がある場合は、key.equal(k)を使用して対応するエントリを見つけます。

どのハッシュアルゴリズムを知っていますか

  1. ハッシュ関数とは、広い範囲を小さな範囲にマッピングすることです。大きな領域を小さな領域にマッピングする目的は、多くの場合、スペースを節約し、データを簡単に保存できるようにすることです。
  2. より有名なものはMurmurHash、MD4、MD5です

文字列ハッシュコードの実装について話します

ここに画像の説明を挿入
StringのhashCode()の計算のアイデアは次のとおりです:重みとして31を取り、各ビットは計算用の文字のASCII値であり、自然なオーバーフローを使用して同等の値を取得します。
ハッシュ演算式は、31 ^(N-1)+ S [1] 31 ^(N-2)+ ... + S [N-1] [0]のようにカウントすることができ
、なぜ31か?素数である
主な理由31は奇数の素数であるため、31 * i = 32 * ii =(i << 5)-iであり、変位と減算を組み合わせたこの種の計算は、一般的な計算よりもはるかに高速です。

JDK1.8のHashMapへの改善

  1. リンクリストの長さが8より大きい場合、リンクリストは赤黒木に変換されることを追加しました。
  2. 高次演算ハッシュアルゴリズムの最適化:h ^(h >>> 16)
  3. 展開後、要素は元の位置にあるか、元の位置で2の累乗に移動され、リンクリストの順序は変更されません。HashMap無限ループの問題を解決しました

ハッシュの競合を解決するときに赤黒木を直接使用しないのはなぜですか?代わりに、最初にリンクリストを使用し、次に赤黒木を使用することを選択しますか?

赤黒木はバランスを維持するために左利き、右利き、および色を変更する操作を実行する必要があるため、単一リンクリストは実行しません。
要素数が8未満の場合、この時点でクエリ操作を実行すると、リンクリスト構造でクエリのパフォーマンスを保証できます。要素が8つを超える場合、クエリを高速化するには赤黒木が必要ですが、新しいノードを追加する効率は低下します。
したがって、最初に赤黒木構造を使用すると、要素が少なすぎて、新しい効率が比較的遅くなり、パフォーマンスの無駄になることは間違いありません。

赤黒木を置き換えるために二分探索木を使用できますか?

はい。ただし、二分探索木は線形構造に縮退する可能性があります。検索のトラバースは非常に遅くなります。

リンクリストが赤黒木になると、いつリンクリストに縮退しますか?

以下のための6リンクリストに戻る時間。リンクリストとツリーの間で頻繁に変換されるのを防ぐために、中央に7の違いがあります。

並行プログラミング環境でのHashMapの問題は何ですか?

  1. (1.8より前の問題)マルチスレッド拡張により、無限ループの問題が発生しました。同時に実行すると、再ハッシュによって要素が循環リンクリストを形成します。
  2. 複数のスレッドに入れると要素が失われる可能性があります
  3. null以外の要素を配置した後、getoutはnullになります

並行性の問題を解決する方法

スレッドセーフなコレクションクラスであるconcurrentHashMapを使用します。注:Hashtableはスレッドセーフですが、実際には非推奨であり、あまり使用されていません。

キーをnull値にすることはできますか?

できる。キーがnullの場合、ハッシュアルゴリズムの最終値は0として計算されます。配列の最初の位置に配置します。
ここに画像の説明を挿入

HashMapのキーとして一般的に使用されるもの

通常、HashMapのキーとしては、IntegerやStringなどの不変のクラスが使用されます。文字列が最も一般的に使用されます。

  1. 文字列は不変であるため、ハッシュコードは作成時にキャッシュされ、再計算する必要はありません。これにより、文字列はマップ内のキーとして非常に適しており、文字列の処理速度は他のキーオブジェクトよりも高速です。これは、HashMapのキーが文字列を使用することが多いということです。
  2. オブジェクトを取得するときにequals()メソッドとhashCode()メソッドが使用されるため、キーオブジェクトがこれら2つのメソッドを正しく書き換えることは非常に重要です。これらのクラスは、hashCode()メソッドとequals()メソッドを既に上書きしています。

HashMapのキーとして変数クラスを使用する場合の問題は何ですか?

hashCodeが変更され、取得できなくなる可能性があります。

HashMapのキーとしてカスタムクラスをどのように実装できますか?

2つの重要なテストポイント:

  • 重写hashcode和equals方法
  • 不変のクラスを設計する方法
  1. 最初の質問の原則:
    (1)2つのオブジェクトは等しい、ハッシュコードは等しくなければならない
    (2)2つのオブジェクトは等しくない、ハッシュコードは必ずしも等しいとは限らない
    (3)ハッシュコードは等しい、2つのオブジェクトは必ずしも等しくない
    (4)ハッシュコード等しくない、2つのオブジェクトは等しくない必要があります
  2. 2番目の質問の設計原則:
    (1)クラスに最終修飾子を追加して、クラスが継承されないようにします。
    (2)すべてのメンバー変数がプライベートである必要があることを確認し、最終変更を追加します
    (3)セッターを含むメンバー変数を変更するメソッドを提供しません
    (4)コンストラクターを介してすべてのメンバーを初期化し、ディープコピー
    初期化メソッドを実行します:
    ここに画像の説明を挿入
    このメソッドはできません不変性を保証します。MyArrayと配列は同じメモリアドレスを指します。ユーザーは、ImmutableDemoの外部で配列オブジェクトの値を変更することにより、myArrayの値を変更できます。
    正しい方法(ディープコピー):(
    ここに画像の説明を挿入
    5)getterメソッドでは、オブジェクト自体を直接返すのではなく、オブジェクトのクローンを作成してオブジェクトのコピーを返します。
    このアプローチは、オブジェクトのリークを防ぎ、内部変数を防ぐためのものです。ゲッターを介してメンバーオブジェクトが取得されないようにした後、メンバー変数が直接操作され、メンバー変数が変更されます。
    文字列型はhashCode()をオーバーロードして、文字列の内容に基づいてHashCode値を返すため、同じ内容の文字列は同じハッシュコードを持ちます

HashMapとHashtableの違い

  1. スレッドが安全かどうか:HashMapのスレッドは安全ではありません。解決策:ConcurrentHashMapを使用してください。ハッシュテーブルはスレッドセーフであり、すべてのメソッドがsynchronized変更されています。
  2. 効率:Hashtableはスレッドセーフであるため、その効率はHashMapよりも低くなります。そして、Hashtableは基本的にもう使用されていません。
  3. ヌルキーの問題:HashMapでは、ヌルが1つ存在する可能性があり、それが配列の最初であり、複数の値がヌルになる可能性があります。ハッシュテーブルはnullをnull値として使用できません。
  4. 初期容量とサイズは異なります。
  • HashMapの初期のデフォルト容量は16であり、拡張されるたびに、その拡張は2の累乗になります。HashMapのサイズが指定されていても、2の累乗に拡張されます。(HashMapの基礎となるtableSizeforメソッドは、その拡張メカニズムを保証します)。
    2の累乗である理由は、衝突を減らし、アクセスを効率化するためです。HashMapは、使用する前に配列の長さ(index = hash%length)も調整する必要があり、残りは対応する配列の添え字を格納するために使用されます。ソースコードの計算方法は次のように書かれています:hash&(length-1)。 也就是说hash%length == hash&(length-1)所以,保证容积是2的n次方,是为了保证在做(length-1)的时候,每一位都能&1`
    (元々書かれていましたが、一度書いています印象を深める)

HashMapマルチスレッド操作によって引き起こされる無限ループの問題

バケット内の値が負荷率*現在のサイズよりも大きい場合は、サイズ変更が必要であることは誰もが知っています。(resize是Rehash中的一个步骤,Rehash包括resize方法和transfer方法)
並行性の下で再ハッシュすると、要素間に循環リンクリストが作成されます。ただし、jdk 1.8 后解决了这个问题マルチスレッドモードでHashMapを使用すると、データの損失などの他の問題が発生するため、マルチスレッドモードでHashMapを使用することはお勧めしません。並行環境では、ConcurrentHashMapをお勧めします。

実際、私は少し混乱しています。jdk1.8はこの問題を解決しますか?それを解決する方法は?関係者はそれを言いましたか?1.8で変更されたのは、リンクリストの長さが8を超えると赤黒木になり、6未満の場合はリンクリストに縮退することです。クエリ効率の問題を解決するために赤黒木を導入していませんか?そして、多くの人々はまた、1.8がまだ再ハッシュの無限ループを引き起こすことを発見しましたか?
ps:公式の意味は次のとおりです。HashMap同時実行の問題は考慮していません。ConcurrentHashMapを使用できるように設計しました。ばかげた同時実行シナリオでは、常にHashMapを使用しないでください。疑い
ここに画像の説明を挿入

ConcurrentHashMapとHashTableの違い

以前、HashMapが同期されていないことはわかっていましたが、公式の推奨事項は、同時実行の問題を解決するためにConcurrentHashMapを使用することです。同時に、HashtableとHashMapがこのデータ構造に非常に似ていることも知っています。多くの場所でHashMapほど良くはありませんが、同期することができます。
そして2つの違い:

  1. 基礎となるデータ構造:
  • JDK1.7のConcurrentHashMapの最下層では、セグメント化された配列+リンクリストが使用されます。jdk1.8では、同じデータ+リンクリスト/赤黒木がHashMapとして採用されます。実際、HashMapは7の時点で配列+リンクリストでもあるため、これを言うのは誤りです。
  • Hashtableの基礎となるデータ構造も、配列+リンクリストの形式になっています。(つまり、時代は変わり、大人も変わっていないので、Hashtableは改善されず、排除されました)
  1. スレッドセーフを実現する方法は異なります。

JDK1.7では、ConcurrentHashMap(セグメントロック)はバケット配列全体をセグメントに分割します。各ロックはコンテナ内のデータの一部のみをロックします。コンテナ内の異なるデータセグメントのデータへのマルチスレッドアクセスはロックされません。競合し、同時アクセス率を上げます。JDK1.8の時点で、セグメントの概念は廃止され、配列+リンクリスト+赤黒木というデータ構造で直接実装され、同時実行制御は同期およびCASによって操作されます。(JDK1.6以降、同期ロックに対して多くの最適化が行われました)全体は最適化されたスレッドセーフなHashMapのように見えます。セグメントのデータ構造はJDK1.8で確認できますが、属性は単純化されています。古いバージョンと互換性があります。

ハッシュテーブル(同じロック):同期を使用してスレッドセーフを確保することは非常に非効率的です。スレッドが同期メソッドにアクセスすると、他のスレッドも同期メソッドにアクセスし、ブロッキング状態またはポーリング状態になる可能性があります。たとえば、putを使用して要素を追加したり、別のスレッドがputを使用して要素を追加したり、getを使用したりすることはできません。ますます激しくなります効率が低くなります。

したがって、Hashtableの実装はconcurrentHashMapの実装よりも簡単ですが。しかし、効率ははるかに悪く、単にロックされており、すべての同期操作で非常に機械的です。
画像ソース:http
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
//www.cnblogs.com/chengxiao/p/6842045.htmlコンテンツソース:JavaGuideWeChatパブリックアカウント:Lonely Smoke

おすすめ

転載: blog.csdn.net/H1517043456/article/details/107537853