1。概要
転載:http://www.javastack.cn/article/2020/hashmap-21-questions/
21のトリッキーなHashMapインタビューの質問、今回はひざまずきます!
1:HashMapのデータ構造?
A:ハッシュテーブル構造(リンクリストハッシュ:配列+リンクリスト)が実装され、配列とリンクリストの利点が組み合わされています。リンクリストの長さが8を超えると、リンクリストは赤黒ツリーに変換されます。
transient Node<K,V>\[\] table;
2:HashMapはどのように機能しますか?
HashMapの最下層は、ハッシュ配列と単一リンクリストです。配列の各要素はリンクリストであり、Node内部クラス(Map.Entry <K、V>インターフェースを実装)によって実装されます。HashMapは、put&getメソッドを介して格納および取得されます。
オブジェクトを保存するときに、K / Vキーの値をput()メソッドに渡します。
①hash(K)メソッドを呼び出してKのハッシュ値を計算し、次に配列の長さを組み合わせて配列の添え字を計算します。
②。配列のサイズを調整します(コンテナー内の要素の数が容量* loadfactorより大きい場合、コンテナーは拡張され、2nにサイズ変更されます)。
③、i。Kのハッシュ値がHashMapに存在しない場合は挿入が行われ、存在する場合は衝突が発生します。
ii。Kのハッシュ値がHashMapに存在し、両方がtrueの等しい値を返す場合、キーと値のペアを更新します。
iii。Kのハッシュ値がHashMapに存在し、両方が偽の等号を返す場合、リンクリストの最後(末尾の補間)または赤黒ツリー(ツリーの追加方法)に挿入されます。(JDK 1.7より前ではヘッド補間が使用され、JDK 1.8ではテール補間が使用されていました)(注:衝突によりリンクリストがTREEIFY_THRESHOLD = 8より大きくなると、リンクリストは赤黒ツリーに変換されます)
オブジェクトを取得するときに、Kをget()メソッドに渡します:①、hash(K)メソッドを呼び出し(Kのハッシュ値を計算)、キー値が配置されているリンクリストの配列添え字を取得します。②、リンクリストを順番にトラバースし、equals()メソッド同じノードリンクリストでK値に対応するV値を検索します。
HashCodeが検索および格納されます。equalsは定性的であり、2つが等しいかどうかを比較します。
3. 2つのオブジェクトのhashCodeが同じ場合はどうなりますか?
hashCodeは同じであるため、必ずしも等しいとは限りません(メソッド比較が等しい)。2つのオブジェクトが配置されている配列の添え字は同じであり、「衝突」が発生します。また、HashMapはリンクリストを使用してオブジェクトを格納するため、このノードはリンクリストに格納されます。なぜハッシュコードとイコールメソッドを書き換えるのですか?ご覧になることをお勧めします。
4.ハッシュの実装を知っていますか?なぜこれを達成したいのですか?
JDK 1.8では、hashCode()の高16ビットXOR(h = k.hashCode())^(h >>> 16)によって実現され、主に速度、効率、品質が向上します。 、システムのオーバーヘッドを削減し、添え字の計算に参加していない上位ビットによって引き起こされる衝突を引き起こしません。
5. XOR演算子を使用する理由
オブジェクトのhashCodeの32ビット値の1ビットが変化する限り、hash()の戻り値全体が変化することが保証されています。衝突を可能な限り減らします。
6. HashMapテーブルの容量を決定する方法は?loadFactorとは何ですか?この容量はどのように変化しますか?この変更によりどのような問題が発生しますか?
①。テーブル配列のサイズは、容量パラメーターによって決定され、デフォルトは16であるか、または構築中に渡すことができ、上限は1 << 30です。
②、loadFactorは負荷係数、主な目的はテーブル配列を動的に拡張する必要があるかどうかを確認することです。デフォルト値は0.75です。たとえば、テーブル配列のサイズが16で負荷係数が0.75の場合、しきい値は12であり、テーブルの実際のサイズが12を超える場合、テーブルには動的拡張が必要です。
③。展開するときは、resize()メソッドを呼び出してテーブルの長さを2倍にします(テーブルの長さではなく、しきい値の長さに注意してください)。
④。データが大きい場合、展開時に性能低下が発生しますが、性能要求の高い場所では致命的となる場合があります。
推奨:なぜHashMapの容量は常に2の累乗なのですか?
7. HashMapのputメソッドのプロセスは何ですか?
回答:「ハッシュ関数を呼び出してキーに対応するハッシュ値を取得し、配列の添え字を計算します。
ハッシュの競合がない場合は、配列に直接配置し、ハッシュの競合がある場合は、リンクリストの形式でリンクリストの後ろに配置します。
リンクリストの長さがしきい値(TREEIFY THRESHOLD == 8)を超える場合、リンクリストは赤黒ツリーに変換され、リンクリストが6未満の場合、赤黒ツリーはリンクリストに変換されます。
ノードのキーがすでに存在する場合は、その値を置き換えます。
コレクション内のキーと値のペアが12より大きい場合は、resizeメソッドを呼び出して配列を拡張します。」
8.アレイ拡張のプロセスは何ですか?
古いアレイの2倍の容量で新しいアレイを作成し、古いアレイのノードのストレージ場所を再計算します。新しい配列のノードの位置は2つしかありません。元の添え字の位置または元の添え字+古い配列のサイズです。
9.ジッパー方式による過度に深いリンクリストの問題、なぜ赤黒木ではなく二分探索木を使用しないのですか?赤黒木を常に使用しないのはなぜですか?
赤黒木を選択する理由は、二分探索木の欠陥を解決するためです。特別な状況では、二分探索木は線形構造になります(これは元のリンクリスト構造と同じで、深い問題が発生します)、トラバース検索非常に遅くなります。提言:インタビュー中に赤黒の木に尋ねたところ、顔が緑色に変わりました。
赤黒木は、新しいデータを挿入した後にバランスを維持するために、左利き、右利き、色変更の操作が必要になる場合があります。赤黒木は、データをすばやく見つけ、リンクリストのクエリ深度の問題を解決するために導入されています。赤黒木は、バランスのとれた二分木であることがわかっています。しかし、「バランス」を維持するために支払うべき代償はありますが、リソースのコストは線形リンクリストをトラバースするよりも少ないため、長さが8を超えると赤黒ツリーが使用されます。リンクリストの長さが非常に短い場合は、まったく使用されません。赤黒木を導入する必要がありますが、導入は遅くなります。
10.赤黒木についてのあなたの意見を教えてください。
各ノードは赤または黒です
ルートノードは常に黒です
ノードが赤の場合、その子ノードは黒である必要があります(逆も同様)
各リーフノードは黒い空のノード(NILノード)です。
ルートノードからリーフノードまたは空の子ノードへの各パスには、同じ数の黒いノード(つまり、同じ黒い高さ)が含まれている必要があります
11. jdk8のHashMapにどのような変更が加えられましたか?
Java 1.8では、リンクリストの長さが8を超えると、リンクリストは赤黒ツリーに変換されます。(バケットの数は64より大きい必要があります。64未満の場合、展開されます。)WeChat公式アカウント:Javaテクノロジースタックに従って、バックグラウンドで返信してください:新機能。私がコンパイルしたN Java新機能のチュートリアルを入手できます。
ハッシュ衝突が発生すると、リンクされたリストの先頭にjava 1.7が挿入され、リンクされたリストの最後にjava 1.8が挿入されます
Java 1.8では、エントリはノードに置き換えられました(ベストの変更)。
12. HashMap、LinkedHashMap、TreeMapの違いは何ですか?
HashMapは他の質問を参照します; WeChatパブリックアカウントに注意してください:Javaテクノロジースタック、バックグラウンドで返信:Java、私がコンパイルしたN Javaコレクションチュートリアルを入手できます。
LinkedHashMapはレコードの挿入順序を保存します。Iteratorを使用してトラバースする場合、最初にフェッチされたレコードを最初に挿入する必要があります。トラバーサルはHashMapよりも低速です。
TreeMapは、保存したレコードをキーに従ってソートできるSortMapインターフェースを実装しています(デフォルトのキー値は昇順で、ソートコンパレーターも指定できます)。
13. HashMap&TreeMap&LinkedHashMapの使用シナリオは何ですか?
一般的に、最も使用されるのはHashMapです。
HashMap:要素をマップに挿入、削除、配置するとき。
TreeMap:キーを自然順序またはカスタム順序でトラバースする必要がある場合。
LinkedHashMap:出力順と入力順が同じ場合。
14. HashMapとHashTableの違いは何ですか?
①、HashMapはスレッドセーフではなく、HashTableはスレッドセーフです。
②スレッドセーフのため、HashTableの効率はHashMapほど効率的ではありません。
③、HashMapでは1つのレコードのキーのみをnullにすることができ、複数のレコードの値をnullにすることができますが、HashTableでは許可されません。
④、HashMapのデフォルトの初期化配列サイズは16、HashTableは11です。前者が拡張すると、2回拡張し、後者は2回拡張します+1。
⑤、HashMapはハッシュ値を再計算する必要がありますが、HashTableはオブジェクトのhashCodeを直接使用します
15. HashMapによく似たJavaの別のスレッドセーフクラスとは何ですか?スレッドセーフでもありますが、スレッドの同期に関して、ハッシュハッシュとHashTableの違いは何ですか?
ConcurrentHashMapクラス(Java並行性パッケージjava.util.concurrentで提供されるスレッドセーフで効率的なHashMap実装)。
HashTableは、synchronizeキーワードを使用してロックする(つまり、オブジェクトをロックする)原則です。
ConcurrentHashMapの場合、セグメント化されたロックはJDK 1.7で採用され、CAS(ロックフリーアルゴリズム)+同期はJDK 1.8で直接採用されます。
16. HashMapとConcurrentHashMapの違いは何ですか?
ロックを除いて、原則として大きな違いはありません。さらに、HashMapのキーと値のペアはnullを許可しますが、ConCurrentHashMapはそれを許可しません。
17. ConcurrentHashMapがHashTableより効率的である理由
HashTableは、ロック(リンクリスト構造全体をロックする)を使用して、同時実行性の問題を処理します。複数のスレッドがロックをめぐって競合するため、簡単にブロックできます。
ConcurrentHashMap
JDK 1.7では、セグメント化されたロック(ReentrantLock +セグメント+ HashEntry)が使用されます。これは、HashMapを複数のセグメントに分割し、各スレッドにロックを割り当て、マルチスレッドアクセスをサポートすることと同じです。ロックの細分性:セグメントに基づいて、複数のHashEntryが含まれます。
CAS +同期+ノード+赤黒木はJDK 1.8で使用されます。ロックの粒度:ノード(最初のノード)(Map.Entry <K、V>を実装)。ロックの粒度が削減されます。
18. ConcurrentHashMapロックメカニズムの具体的な分析(JDK 1.7 VS JDK 1.8)?
JDK 1.7では、セグメント化されたロックメカニズムを使用して、同時更新操作を実装しています。最下層では、2つのコア静的内部クラス、SegmentとHashEntryを含む配列+リンクリストストレージ構造を使用しています。
①セグメントはReentrantLock(再入可能ロック)を継承してロックとして機能し、各セグメントオブジェクトは各ハッシュマップの複数のバケットを保護します。
②、HashEntryは、マッピングテーブルのキーと値のペアをカプセル化するために使用されます。
③、各バケットは複数のHashEntryオブジェクトによってリンクされたリンクリストです
JDK 1.8では、ノード+ CAS + Synchronizedを使用して、同時実行の安全性を確保しています。クラスセグメントをキャンセルし、テーブル配列を直接使用してキーと値のペアを格納します。HashEntryオブジェクトで構成されるリンクリストの長さがTREEIFY_THRESHOLDを超えると、リンクリストは赤黒ツリーに変換され、パフォーマンスが向上します。下のレイヤーは、配列+リンクリスト+赤黒ツリーに変更されます。
19. JDK 1.8のConcurrentHashMap、なぜ再入可能ロックReentrantLockの代わりに同期された組み込みロックを使用するのですか?
①粒子サイズが小さくなります。
②JVM開発チームは同期をあきらめておらず、JVMに基づく同期最適化の最適化スペースはより大きく、より自然です。
③。大量のデータ操作の下では、JVMのメモリプレッシャーのために、APIベースのReentrantLockがより多くのメモリを消費します。
20. ConcurrentHashMapの簡単な紹介?
①重要な定数:
プライベートな一時的な揮発性int sizeCtl;
負の数の場合、-1は初期化を意味し、-NはN-1スレッドが拡張していることを意味します。
0の場合、テーブルが初期化されていないことを意味します。
別の正数の場合、初期化または次の拡張のサイズを示します。
②データ構造:
ノードはストレージ構造の基本単位であり、HashMapのエントリを継承し、データを格納するために使用されます。
TreeNodeはNodeを継承しますが、データ構造は赤黒木のストレージ構造であり、赤黒ツリーにデータを格納するために使用されるバイナリツリー構造に置き換えられます。
TreeBinは、TreeNodeをカプセル化し、赤黒木を変換するためのいくつかの条件とロック制御を提供するコンテナーです。
③オブジェクトを格納する場合(put()メソッド):
初期化されていない場合は、initTable()メソッドを呼び出して初期化します。
ハッシュの競合がない場合は、直接CASロックフリー挿入。
容量を拡張する必要がある場合は、最初に容量を拡張します。
ハッシュの競合がある場合、ロックはスレッドの安全性を確保するために追加されます。2つのケースがあります。1つはリンクリストが最後まで直接トラバースされて挿入される場合、もう1つは赤黒ツリーが赤黒ツリー構造に従って挿入される場合です。
リンクされたリストの数がしきい値8より大きい場合は、最初に赤黒のツリー構造に変換する必要があり、breakは再びループに入ります
追加が成功した場合は、addCount()メソッドを呼び出してサイズをカウントし、拡張が必要かどうかを確認します。
④拡張方式transfer():デフォルト容量は16で、拡張すると元の2倍の容量になります。
helpTransfer():複数のワーカースレッドを一緒に呼び出して容量を拡張し、効率を高めます。
anオブジェクトを取得する場合(get()メソッド):
ハッシュ値を計算し、テーブルインデックスの位置を特定し、最初のノードが一致する場合は返します。
展開を検出すると、展開ノードをマークしているノードのForwardingNode.find()メソッドを呼び出し、ノードを検索して、一致する場合は戻ります。
上記のいずれにも一致しない場合、ノードを下にたどり、一致した場合は戻り、そうでない場合は最後にnullを返します。
21. ConcurrentHashMapの同時実行性とは何ですか?
ロックの競合なしでプログラムが実行されているときに、ConccurentHashMapを同時に更新できるスレッドの最大数。デフォルトは16で、コンストラクターで設定できます。
ユーザーが並行性を設定すると、ConcurrentHashMapはこの値以上の最小の2のべき乗の指数を実際の並行性として使用します(ユーザーが並行性を17に設定した場合、実際の並行性は32です)。