2023 コレクション コンテナ ステレオタイプ エッセイ - 面接の質問

収集コンテナ

収集コンテナの概要

コレクションとは何ですか

**コレクション フレームワーク:** データを保存するためのコンテナー。

コレクション フレームワークは、コレクションを表現および操作するための統一された標準アーキテクチャです。どのコレクション フレームワークにも、外部インターフェイス、インターフェイス実装、コレクション操作のアルゴリズムという 3 つの主要な部分が含まれています。

**インターフェイス:** は、コレクションの抽象データ型を表します。このインターフェイスにより、特定の実装に注意を払うことなくコレクションを操作できるため、「ポリモーフィズム」が実現されます。オブジェクト指向プログラミング言語では、仕様を形成するためにインターフェイスがよく使用されます。

**実装: **コレクション インターフェイスの特定の実装は、再利用性の高いデータ構造です。
**アルゴリズム:** 検索、並べ替えなど、特定のコレクション フレームワークのインターフェイスを実装するオブジェクトに対して有用な計算を実行する方法。同じインターフェイスが複数のクラスで実装されている場合、同じメソッドの動作が異なる可能性があるため、これらのアルゴリズムは通常多態性です。実際、アルゴリズムは再利用可能な関数です。プログラミングの労力が軽減されます。

コレクション フレームワークを使用すると、プログラムを適切に動作させるための低レベルの設計に焦点を当てるのではなく、有用なデータ構造とアルゴリズムを提供することで、プログラムの重要な部分に集中できます。

関連のない API 間での相互運用が容易になるため、オブジェクトを適応させたり、これらの API を組み合わせるためにコードを変換したりするための大量のコードを作成する必要がなくなります。プログラムの速度と品質が向上します。

コレクションの特徴

コレクションには 2 つの主な特徴があります。

  • オブジェクトはデータをカプセル化するため、より多くのオブジェクトを保存する必要があります。コレクションはオブジェクトを保存するために使用されます。
  • オブジェクトの数が決まっている場合は配列を使用でき、オブジェクトの数が不明な場合はコレクションを使用できます。コレクションは可変長であるためです。

コレクションと配列の違い

  • 配列は固定長ですが、コレクションは可変長です。
  • 配列には基本データ型だけでなく参照データ型も格納できますが、コレクションには参照データ型のみを格納できます。
  • 配列に格納される要素は同じデータ型である必要がありますが、コレクションに格納されるオブジェクトは異なるデータ型であってもかまいません。

**データ構造:** はコンテナにデータを保存する方法です。

回収容器にはたくさんの種類があります。各コンテナの特性が異なるため、原則として各コンテナの内部データ構造も異なります。

回収容器を上方に連続的に取り出す過程で、回収システムが出現します。システム使用の原則: トップレベルのコンテンツを参照してください。基礎となるオブジェクトを作成します。

コレクション フレームワークを使用する利点

  1. 能力の自己成長。
  2. 高性能のデータ構造とアルゴリズムを提供し、コーディングを容易にし、プログラムの速度と品質を向上させます。3
  3. 異なる API 間の相互運用性を可能にし、API 間でコレクションをやり取りできます。
  4. このコレクションは簡単に拡張したり書き換えたりして、コードの再利用性と操作性を向上させることができます。
  5. JDK に付属のコレクション クラスを使用すると、コードのメンテナンスと新しい API の学習のコストを削減できます。

よく使用されるコレクション クラスは何ですか?

Map インターフェイスと Collection インターフェイスは、すべてのコレクション フレームワークの親インターフェイスです。

  1. Collection インターフェースのサブインターフェースには、Set インターフェースと List インターフェースが含まれます。
  2. Map インターフェースの実装クラスには主に、HashMap、TreeMap、Hashtable、ConcurrentHashMap、Properties などが含まれます。
  3. Set インターフェースの実装クラスには主に HashSet、TreeSet、LinkedHashSet などが含まれます。
  4. List インターフェースの実装クラスには主に ArrayList、LinkedList、Stack、Vector などが含まれます。

リスト、セット、マップの違いは何ですか? List、Set、Map は Collection インターフェイスを継承しますか? 要素にアクセスするときの List、Map、Set の 3 つのインターフェイスのそれぞれの特徴は何ですか?

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-nX090D2S-1692508006551) (02-Java コレクション コンテナのインタビュー質問 (2020 最新版)-)キーポイント.assets/コレクション.png)]

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-4o9DbADS-1692508006552) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-)キー.アセット/マップ.png)]

Java コンテナは、コレクションとマップの 2 つのカテゴリに分かれており、コレクションのサブインターフェイスには、セット、リスト、キューの 3 つのサブインターフェイスが含まれます。一般的に Set と List を使用しますが、Map インターフェイスはコレクションのサブインターフェイスではありません。

コレクション collection には主に List と Set の 2 つのインターフェイスがあります。

  • リスト: 順序付けされたコンテナー (要素がコレクションに格納される順序は、要素が取り出される順序と同じです)。要素は繰り返すことができ、複数の null 要素を挿入でき、要素にはインデックスがあります。一般的に使用される実装クラスは、ArrayList、LinkedList、Vector です。
  • Set: 順序付けされていない (保管順序と引き出し順序が一致しない可能性がある) コンテナーは重複した要素を保管できませんが、null エレメントを 1 つだけ保管でき、エレメントの一意性が保証されなければなりません。Set インターフェイスの一般的な実装クラスは、HashSet、LinkedHashSet、および TreeSet です。

マップはキーと値のペアのコレクションであり、キー、値、および値の間のマッピングを保存します。キーは順序付けされておらず一意であり、値には順序は必要なく、重複が許可されます。Map は Collection インターフェイスから継承しません。Map コレクションから要素を取得する場合、キー オブジェクトが指定されている限り、対応する値オブジェクトが返されます。

Map の共通実装クラス: HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap

収集フレームワークの基礎となるデータ構造

コレクション

**リスト**

  • 配列リスト: オブジェクト配列
  • ベクトル: オブジェクト配列
  • LinkedList: 双方向循環リンク リスト

**セット **

  • HashSet (順序なし、一意): HashMap に基づいて実装され、基礎となる層は HashMap を使用して要素を格納します
  • LinkedHashSet: LinkedHashSet は HashSet を継承しており、その内部は LinkedHashMap を通じて実現されます。これは、Hashmap に基づいて内部実装される前述の LinkedHashMap に少し似ていますが、それでも少し異なります。
  • TreeSet (順序付き、一意): 赤黒ツリー (自己バランス型ソート バイナリ ツリー) マップ
  • HashMap: JDK1.8以前では、HashMapは配列+リンクリストで構成されており、配列がHashMapの本体であり、リンクリストは主にハッシュの競合を解決するために存在します(競合を解決する「ジッパーメソッド」)。ハッシュの競合が
    解決されます。 リンク リストの長さがしきい値 (デフォルトでは 8) を超える場合、検索時間を短縮するためにリンク リストが赤黒ツリーに変換されます。
  • LinkedHashMap: LinkedHashMap は HashMap を継承しているため、その最下層は依然としてジッパー ハッシュ構造に基づいており、配列とリンク リストまたは赤黒ツリーで構成されています。さらに、LinkedHashMap は、上記の構造に基づいて二重リンク リストを追加するため、上記の構造はキーと値のペアの挿入順序を維持できます。同時に、リンクリストに対して対応する操作を実行することで、アクセスシーケンスに関連するロジックが実現されます。
  • HashTable: 配列 + リンク リストで構成されます。配列は HashMap の本体であり、リンク リストは主にハッシュの競合を解決するために存在します。
  • TreeMap: 赤黒ツリー (自己バランス型ソート バイナリ ツリー)

どのコレクション クラスがスレッド セーフですか?

  • Vector: arraylist よりも 1 つ多くの同期メカニズム (スレッド セーフ) を備えていますが、効率が低いため、現在使用することはお勧めできません。Web アプリケーション、特にフォアグラウンド ページでは、効率 (ページの応答速度) が優先されることがよくあります。
  • statck: スタック クラス、先入れ後出し。
  • ハッシュテーブル: ハッシュマップよりもスレッドセーフです。
  • enumeration: 列挙、イテレータと同等。

Java コレクションのフェイルファスト機構は「フェイルファスト」ですか?

これは Java コレクションのエラー検出メカニズムであり、複数のスレッドがコレクションの構造変更を実行すると、フェイルファスト メカニズムが発生する可能性があります。

例: 2 つのスレッド (スレッド 1、スレッド 2) があり、スレッド 1 が反復子を介してコレクション A 内の要素を走査し、ある時点でスレッド 2 がコレクション A の構造を変更するとします (これは構造の変更であり、変更ではありません)。シンプルなコレクション要素の内容を変更する場合)、プログラムはこの時点で ConcurrentModificationException をスローし、フェイルファスト メカニズムが発生します。

理由: イテレータはトラバース中にコレクションのコンテンツに直接アクセスし、トラバース中に modCount 変数を使用します。トラバーサル中にコレクションの内容が変更されると、modCount の値が変更されます。反復子が次の要素を走査する前に hashNext()/next() を使用するたびに、modCount 変数が期待される modCount 値であるかどうかをチェックし、そうである場合は走査に戻り、そうでない場合は例外をスローして走査を終了します。

解決:

  1. 走査プロセス中に、modCount の値の変更を伴うすべての場所に synchronized が追加されます。
  2. CopyOnWriteArrayList を使用して ArrayList を置き換えます

コレクションが変更できないようにするにはどうすればよいですか?

Collections.unmodifiableCollection(Collection c) メソッドを使用して読み取り専用コレクションを作成すると、コレクションを変更する操作によって Java.lang.UnsupportedOperationException がスローされるようになります。サンプルコードは次のとおりです。

 List<String> list = new ArrayList<>(); 
 list. add("x"); 
 Collection<String> clist = Collections. unmodifiableCollection(list); 
 clist. add("y"); // 运行时此行报错 
 System. out. println(list. size()); 

コレクションインターフェース

リストインターフェース

イテレータ イテレータとは何ですか?

Iterator インターフェイスは、コレクションを走査するためのインターフェイスを提供します。iterator メソッドを使用して Collection からイテレータ インスタンスを取得できます。Java コレクション フレームワークの Enumeration はイテレーターに置き換えられ、イテレーターは呼び出し元が反復中に要素を削除できるようにします。

イテレータの使い方 特徴は何ですか?

イテレータの使用コードは次のとおりです。

1	List<String> list = new ArrayList
2	Iterator<String> it = list. iterator
3	while(it. hasNext()){
4	String obj = it. next();
5	System. out. println(obj);
6	}

Iterator の特徴は一方向にしかトラバースできないことですが、現在のトラバースでは確実にトラバースできるので安全です。

コレクションの要素が変更されると、ConcurrentModificationException がスローされます。

トラバース中にコレクション内の要素を削除するにはどうすればよいですか?

反復中にコレクションを変更する唯一の正しい方法は、次のように Iterator.remove() メソッドを使用することです。

1   Iterator<Integer> it = list.iterator();
2	while(it.hasNext()){
3	*// do something*
4	it.remove();5	}

一般的なエラー コードは次のとおりです。

1	for(Integer i : list){
2	list.remove(i)
3	}

上記のエラー コードを実行すると、ConcurrentModificationException 例外が報告されます。これは、foreach(for(Integer i : list)) ステートメントが使用されると、リストを走査する反復子が自動的に生成されますが、同時にリストが Iterator.remove() によって変更されるためです。Java では通常、あるスレッドがコレクションを走査している間に、あるスレッドがコレクションを変更することはできません。

Iterator と ListIterator の違いは何ですか?

  • Iterator は Set コレクションと List コレクションを走査できますが、ListIterator は List のみを走査できます。
  • Iterator は一方向にのみトラバースできますが、ListIterator は両方向にトラバースできます (前方/後方トラバース)。
  • ListIterator は Iterator インターフェイスを実装し、要素の追加、要素の置換、前後の要素のインデックス位置の取得などの追加関数を追加します。

リストを反復処理するさまざまな方法には何がありますか? 各メソッドの実装原理は何ですか? Java でのリスト走査のベスト プラクティスは何ですか?

トラバースメソッドは次のとおりです。

  1. for ループはカウンターに基づいてトラバースします。コレクションの外側にカウンターを保持し、各位置の要素を順番に読み取り、次の要素が読み取られた時点で停止します。

  2. イテレータトラバーサル、イテレータ。イテレーターはオブジェクト指向の設計パターンであり、その目的は、さまざまなデータ コレクションの特性を保護し、トラバースするコレクションのインターフェイスを統一することです。Java はコレクションの Iterator パターンをサポートします。

  3. foreach はループスルーします。Foreach も Iterator の形で実装されており、使用する際に Iterator や counter を明示的に宣言する必要はありません。利点は、コードが簡潔でエラーが発生しやすいことですが、欠点は、単純な走査しか実行できず、削除や置換などの走査プロセス中にデータ セットを操作できないことです。

ベスト プラクティス: Java コレクション フレームワークは、List 実装がランダム アクセスをサポートするかどうかをマークする RandomAccess インターフェイスを提供します。

  • データ コレクションがこのインターフェイスを実装している場合、それはランダム アクセスをサポートし、ArrayList などの位置による要素の読み取りの平均時間計算量は O(1) であることを意味します。
  • このインターフェイスが実装されていない場合は、LinkedList などのランダム アクセスがサポートされていないことを意味します。推奨される方法は、ランダム アクセスをサポートするリストを for ループで走査できることです。それ以外の場合は、Iterator または foreach で走査することをお勧めします。

ArrayList の長所と短所について話す

ArrayList の利点は次のとおりです。

  • ArrayList の最下層は、ランダム アクセス モードである配列として実装されます。ArrayList は RandomAccess インターフェイスを実装しているため、検索は非常に高速です。
  • ArrayList は要素を 1 つずつ順番に追加する場合に非常に便利です。

ArrayList の欠点は次のとおりです。

  • 要素を削除する場合は、要素のコピー操作が必要です。コピーする要素が多い場合、パフォーマンスが消費されます。
  • 要素を挿入するときは、要素のコピー操作も行う必要があり、欠点は上記と同じです。

ArrayList は、順次追加やランダム アクセスのシナリオに適しています。

配列とリストの間の変換を実現するにはどうすればよいですか?

配列からリストへ: Arrays.asList(array) を使用して変換します。

リストから配列へ: List に付属の toArray() メソッドを使用します。コード例:

1	// list to array
2	List<String> list = new ArrayList<String>();
3	list.add("123");
4	list.add("456");
5	list.toArray();
6
7	// array to list
8	String[] array = new String[]{"123","456"};
9	Arrays.asList(array);

ArrayList と LinkedList の違いは何ですか?

  • データ構造実装: ArrayList は動的配列のデータ構造実装であり、LinkedList は二重リンク リストのデータ構造実装です。
  • ランダム アクセスの効率: LinkedList は線形データ ストレージ方式であるため、ポインタを前から後ろに移動して検索する必要があるため、ArrayList はランダム アクセスにおいて LinkedList よりも効率的です。
  • 追加および削除の効率: ArrayList の追加および削除操作は配列内の他のデータの添字に影響を与えるため、LinkedList は先頭および末尾以外の追加および削除操作において ArrayList よりも効率的です。
  • メモリ空間の占有: LinkedList ノードはデータの保存に加えて 2 つの参照 (前の要素を指し、もう 1 つは次の要素を指す) を格納するため、LinkedList は ArrayList よりも多くのメモリを占有します。
  • スレッド セーフ: ArrayList と LinkedList は両方とも同期されていません。つまり、スレッド セーフは保証されていません。

一般に、コレクション内の要素を頻繁に読み取る必要がある場合は ArrayList が推奨され、挿入および削除操作が多い場合は LinkedList が推奨されます。

補足: データ構造に基づく二重リンクリスト

二重リンク リストは、二重リンク リストとも呼ばれ、リンク リストの一種であり、その中の各データ ノードには 2 つのポインタがあり、それぞれ直接後続ノードと直接先行ノードを指します。したがって、二重リンク リスト内の任意のノードから開始して、その先行ノードと後続ノードに簡単にアクセスできます。

ArrayList と Vector の違いは何ですか?

どちらのクラスも List インターフェイスを実装し (List インターフェイスは Collection インターフェイスを継承します)、どちらも順序付きコレクションです。

スレッド セーフ: Vector は Synchronized を使用して、スレッド セーフなスレッド同期を実現します。

ArrayList はスレッドセーフではありません。

パフォーマンス: パフォーマンスの点では、ArrayList は Vector よりも優れています。

拡張: ArrayList と Vector は両方とも、実際のニーズに応じて容量を動的に調整できますが、

ベクトルの展開は毎回 2 倍になりますが、ArrayList は 50% しか増加しません。

Vector クラスのすべてのメソッドは同期です。Vector ペアは 2 つのスレッドから安全にアクセスできます

同様ですが、スレッドが Vector にアクセスすると、コードは同期操作に多くの時間を費やします。

Arraylist は同期されないため、スレッド セーフが必要ない場合は Arraylist を使用することをお勧めします。

データを挿入するとき、ArrayList、LinkedList、Vector のどれが速いですか? ArrayList、Vector、LinkedListのストレージ性能と特徴を説明してください。

ArrayList、LinkedList、Vector の基礎となる実装はすべて、配列を使用してデータを保存します。配列

要素の追加および挿入では、実際に格納されているデータよりも要素数が多くなります。いずれもシリアル番号による要素の直接インデックス付けが可能ですが、要素の挿入には配列要素の移動などのメモリ操作が含まれるため、データのインデックス付けは高速ですが、データの挿入は遅くなります。 。

Vector のメソッドは synchronized で変更されるため、Vector はスレッドセーフなコンテナですが、パフォーマンスは ArrayList よりも劣ります。

LinkedList はストレージに二重リンク リストを使用します。シリアル番号によるデータのインデックス付けには前方または後方の走査が必要ですが、データを挿入するときは、現在のアイテムの前と後のアイテムを記録するだけでよいため、LinkedList の方が高速に挿入できます。

マルチスレッドシナリオで ArrayList を使用するにはどうすればよいですか?

ArrayList はスレッドセーフではありません。マルチスレッドのシナリオが発生した場合は、コレクションを使用できます。

synchronizedList メソッドは、使用する前にスレッドセーフなコンテナーに変換します。たとえば次のようになります。

1	List<String> synchronizedList = Collections.synchronizedList(list);
2	synchronizedList.add("aaa");
3	synchronizedList.add("bbb");
4
5	for (int i = 0; i < synchronizedList.size(); i++) {
6	System.out.println(synchronizedList.get(i));
7	}

ArrayList の elementData が transient で装飾されているのはなぜですか? ArrayList の配列は次のように定義されます。

1 private transient Object[] elementData;

ArrayList の定義をもう一度見てください。

1	public class ArrayList<E> extends AbstractList<E>
2	implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList が Serializable インターフェイスを実装していることがわかります。これは、ArrayList がシーケンスをサポートしていることを意味します。

変化。transient の役割は、elementData 配列がシリアル化されることが予期されていないことを示し、writeObject 実装が書き換えられることです。

1 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOE xception{ 
2  *// Write out element count, and any hidden stuff* 
3  int expectedModCount = modCount; 
4  s.defaultWriteObject(); 
5  *// Write out array length* 
6  s.writeInt(elementData.length); 
7  *// Write out all elements in the proper order.* 
8  for (int i=0; i<size; i++) 
9  s.writeObject(elementData[i]); 
10  if (modCount != expectedModCount) { 
11  throw new ConcurrentModificationException();
12 }

各シリアル化では、まず、defaultWriteObject() メソッドを呼び出して ArrayList 内の非一時的要素をシリアル化し、次に elementData を走査し、保存されている要素のみをシリアル化します。これにより、シリアル化の速度が向上するだけでなく、シリアル化の速度も低下します。サイズ。

リストとセットの違い

List と Set は Collection インターフェイスから継承されます

リストの機能: 順序付けされたコンテナー (要素がコレクションに格納される順序は、要素が取り出される順序と同じです)、要素を繰り返すことができ、複数の null 要素を挿入でき、要素にはインデックスがあります。一般的に使用される実装クラスは ArrayList、

LinkedList と Vector。

セット機能: 順序付けされていない (保管順序と引き出し順序が一致しない可能性がある) コンテナーは、重複した要素を保管できず、1 つの null エレメントのみを保管でき、エレメントの一意性が保証されなければなりません。Set インターフェイスの一般的な実装クラスは次のとおりです。

HashSet、LinkedHashSet、および TreeSet。

さらに、List は for ループ、つまり添字による走査をサポートしており、イテレーターも使用できますが、set は順序が乱れており、添字を使用して目的の値を取得することはできないため、反復のみを使用できます。

セットとリストの比較

Set: 要素の取得は非効率的ですが、削除と挿入は効率的で、挿入と削除によって要素の位置は変更されません。

リスト: 配列と同様に、リストは動的に拡大する可能性があり、要素の検索は効率的ですが、要素の挿入と削除は他の要素の位置が変更されるため非効率的です。

インターフェースの設定

HashSetの実装原理について教えてください。

HashSet は HashMap に基づいて実装され、HashSet の値は HashMap のキーに格納されます。

HashMap の値は PRESENT に統一されているため、HashSet の実装は比較的単純であり、関連する HashSet の操作は基本的に基盤となる HashMap の関連メソッドを直接呼び出すことで完了します。

HashSet では値の重複は許可されません。

HashSet は重複をどのようにチェックしますか? HashSet はどのようにしてデータが反復不可能であることを保証しますか?

HashSetに()要素を追加する場合、要素の有無の判断基準はハッシュ値の比較だけでなく、equlesメソッドとの比較も併用します。

HashSet の add() メソッドは、HashMap の put() メソッドを使用します。

HashMapのキーは一意であり、ソースコードを見るとHashSetに追加した値がHashMapのキーとして使用されており、HashMap内のK/Vが同じ場合は古いものが新しいもので上書きされることがわかります。 V

V、古い V を返します。したがって、これは繰り返されません (HashMap は、最初にハッシュコードを比較し、次に等しいかどうかを比較することで、キーが等しいかどうかを比較します)。

以下は HashSet のソース コードの一部です。

1	private static final Object PRESENT = new Object();
2	private transient HashMap<E,Object> map;
3
4	public HashSet() {
<>
5	map = new HashMap ();
6	}
7
8	public boolean add(E e) {
9	// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
10	return map.put(e, PRESENT)==null;
11	}

hashCode()およびequals()の関連規定:

  1. 2 つのオブジェクトが等しい場合、ハッシュコードも同じでなければなりません

  2. 2 つのオブジェクトが等しい場合、両方の等しいメソッドに対して true を返します。

  3. 2 つのオブジェクトは同じハッシュコード値を持ちますが、必ずしも等しいとは限りません

  4. 要約すると、equals メソッドがオーバーライドされている場合は、hashCode メソッドもオーバーライドする必要があります。

  5. hashCode() のデフォルトの動作は、ヒープ上のオブジェクトに対して一意の値を生成することです。hashCode() がオーバーライドされない場合、このクラスの 2 つのオブジェクトは等しくありません (2 つのオブジェクトが同じデータを指している場合でも)。

== と等しいの違い

  1. == は、2 つの変数またはインスタンスが同じメモリ空間を指しているかどうかを判断します。等しいは、2 つの変数またはインスタンスが指しているメモリ空間の値が同じかどうかを判断します。

  2. メモリアドレスを比較することを意味します。equals()は文字列3の内容を比較することです。ガイドが同じかどうかはequals()で値が同じかどうかを参照

HashSet と HashMap の違い

ハッシュマップ ハッシュセット
マップインターフェイスを実装しました Set インターフェイスを実装します。
キーと値のペアを保存する オブジェクトのみを保存する
put() を呼び出してマップに要素を追加します add() メソッドを呼び出して要素を Set に追加します。
HashMap はキー (Key) を使用してハッシュコードを計算します HashSet は、メンバー オブジェクトを使用してハッシュコード値を計算します。ハッシュコードは 2 つのオブジェクトで同じである可能性があるため、オブジェクトの等しいかどうかを判断するために、equals() メソッドが使用されます。2 つのオブジェクトが異なる場合は、false を返します。
HashMap はオブジェクトの取得に一意のキーを使用するため、HashSet よりも高速です HashSet は HashMap よりも遅い

BlockingQueueとは何ですか?

java.util.concurrent.BlockingQueue は、要素を取得または削除するときにキューが空でなくなるまで待機し、要素を追加するときにキューに空き領域ができるまで待機するキューです。BlockingQueue インターフェイスは Java コレクション フレームワークの一部であり、主にプロデューサー/コンシューマー パターンを実装するために使用されます。すべて BlockingQueue 実装クラスで処理されるため、プロデューサーがスペースを利用できるようになるまで、またはコンシューマーがオブジェクトを利用できるようになるのを待つ必要はありません。Java は、ArrayBlockingQueue などの集中型 BlockingQueue の実装を提供します。

LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue など Queueのpoll()とremove()の違いは何ですか?

  • 同じ点: どちらも最初の要素を返し、キュー内の返されたオブジェクトを削除します。
  • 違い: 要素がない場合、poll() は null を返し、remove() は直接 NoSuchElementException をスローします。

コード例:

1	Queue<String> queue = new LinkedList<String>();
2	queue. offer("string"); // add
3	System. out. println(queue. poll());
4	System. out. println(queue. remove());
5	System. out. println(queue. size());

マップインターフェース

HashMapの実装原理について教えてください。

HashMap の概要: HashMap は、ハッシュ テーブルに基づく Map インターフェイスの非同期実装です。この実装では、すべてのオプションのマップ操作が提供され、null 値と null キーが許可されます。このクラスはマッピングの順序を保証せず、特にこの順序が不変であることを保証しません。

HashMap のデータ構造: Java プログラミング言語には 2 つの基本構造があり、1 つは配列、もう 1 つはアナログ ポインター (参照) です。すべてのデータ構造はこれら 2 つの基本構造を使用して構築できますが、HashMap はそうではありません例外。HashMap は実際には、配列とリンク リストを組み合わせた「リンク リスト ハッシュ」データ構造です。

HashMapはハッシュアルゴリズムに基づいて実装されています

  1. 要素をハッシュマップに入れるとき、再ハッシュするキーの hashCode を使用して、配列内の現在のオブジェクトの要素の添字を計算します。

  2. 保存する際に、同じハッシュ値を持つキーが現れた場合、この時点で 2 つの状況が考えられます。(1) キーフェーズの場合

キーが同じ場合は元の値を上書きします (2) キーが異なる場合 (競合が発生した場合)、現在のキーと値をリンク リストに追加します。

  1. 取得する際には、ハッシュ値に対応する添字を直接求め、さらにキーが同一かどうかを判断して、対応する値を求める。

  2. 上記のプロセスを理解すると、HashMap がハッシュ競合の問題をどのように解決するかを理解するのは難しくありません。その核心は、配列ストレージ方式を使用し、競合するキーのオブジェクトをリンクされたリストに入れることです。 、リンクされたリストでさらに比較が行われます。

HashMap の実装は Jdk 1.8 で最適化されていることに注意してください。リンクされたリスト内のノード データが 8 を超える場合

その後、リンク リストはクエリ効率を向上させるために、元の O(n) から O(logn) に赤黒ツリーに変換されます。

JDK1.7とJDK1.8のHashMapの違いは何ですか? HashMap の基礎となる実装

Java には、データを格納するための 2 つの比較的単純なデータ構造、配列とリンク リストがあります。配列の特性は、アドレス指定は簡単だが、挿入と削除は難しい、リンク リストの特性は、アドレス指定は難しいが、挿入と削除は簡単であるため、配列とリンク リストを組み合わせて、それぞれの利点を活かします。ジッパーメソッドと呼ばれるメソッドは、ハッシュの競合を解決できます。

JDK1.8以前

JDK1.8以前では、ジッパーメソッドが使用されていました。ジッパーメソッド: リンクされたリストと配列を結合します。つまり、リンク リストの配列を作成し、配列内の各セルがリンク リストになります。ハッシュの競合がある場合は、競合する値をリンク リストに追加するだけです。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-ry5BaX0U-1692508006553) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-)キーポイント.assets/image- 20201109162525820.png)]

JDK1.8以降

以前のバージョンと比較して、jdk1.8 はハッシュ競合の解決に大きな変更を加えました. リンク リストの長さがしきい値 (デフォルトでは 8) より大きい場合、リンク リストは赤黒ツリーに変換され、検索時間。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-LtupEEp7-1692508006553) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-)キーポイント.assets/clip_image001.jpg)]

JDK1.7 と JDK1.8 の比較

JDK1.8 は主に次の問題を解決または最適化します。

  1. サイズ拡張の最適化

  2. 赤黒ツリーが導入されました。その目的は、単一のリンク リストが長すぎてクエリ効率に影響を与えるのを避けることです。赤黒ツリー アルゴリズムを参照してください。

  3. マルチスレッドの無限ループ問題は解決されましたが、依然としてスレッドセーフではなく、マルチスレッド時にデータ損失が発生する可能性があります。

違う JDK1.7 JDK1.8
収納構造 配列 + リンクされたリスト 配列 + リンクされたリスト + 赤黒ツリー
初期化メソッド 別の関数: inflateTable() 拡張関数resize()に直接統合されています。
ハッシュ値の計算方法 摂動処理 = 9 つの摂動 = 4 ビット演算 + 5 つの XOR 演算 摂動処理 = 2 摂動 = 1 ビット演算 + 1 XOR 演算
データ保存のルール 競合がない場合は配列を保存し、競合がある場合はリンクリストを保存します。 競合がない場合、配列を保存します。競合とリンク リストの長さが 8 未満の場合: 単一のリンク リストを保存します。競合とリンク リストの長さ > 8: ツリーと赤黒ツリーを保存します。
データ挿入メソッド 先頭挿入方法(元の位置のデータを最後の1ビットに移動し、その位置にデータを挿入) 末尾挿入方法(リンクリスト/赤黒ツリーの末尾に直接挿入)
容量拡張後の保管場所の計算方法 すべての計算は元の方法に従って実行されます (つまり、ハッシュコード ->> 妨害関数 ->> (h&length h-1)) 容量拡張後の法則に従って計算します(つまり、容量拡張後の位置 = 元の位置または元の位置 + 旧容量)

HashMapのputメソッドの具体的な処理は?

入れると、まずキーのハッシュ値を計算します ここでハッシュメソッドを呼び出します 実際にハッシュメソッドは key.hashCode() と key.hashCode() >>> 16 で XOR 演算を行い、上位 16bit が0、1 で埋められます。数値と 0 は変更されずに XOR されます。したがって、ハッシュ関数の近似関数は次のようになります。上位 16 ビットは変更されず、下位 16 ビットと上位 16 ビットは変更されません。

16 ビットは衝突を減らすために XOR を実行します。関数のコメントによると、バケット配列のサイズは

2 の累乗、添字インデックス = (table.length - 1) & ハッシュを計算します。ハッシュ処理が実行されない場合、ハッシュが有効になるのは下位の数ビットのみと同等です。ハッシュ、設計者は速度、機能、品質を総合的に考慮し、高 16 ビットと低 16 ビット XOR を使用して単純に衝突を処理し軽減します。

JDK8 では、衝突時のパフォーマンスを向上させるために、複雑度 O(logn) のツリー構造が使用されます。

putValメソッド実行フローチャート

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-6T3PqxSu-1692508006554) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-) focus.assets/image- 20201109162901394.png)]

1 public V put(K key, V value) {
2 return putVal(hash(key), key, value, false, true);
3 }
4
5 static final int hash(Object key) {
6 int h;
7 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
8 }
9
10 //实现Map.put和相关方法
11 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
12 boolean evict) {
13 Node<K,V>[] tab; Node<K,V> p; int n, i;
14 // 步骤①:tab为空则创建
15 // table未初始化或者长度为0,进行扩容
16 if ((tab = table) == null || (n = tab.length) == 0)
17 n = (tab = resize()).length;
18 // 步骤②:计算index,并对null做处理
19 // (n ‐ 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这
个结点是放在数组中)
20 if ((p = tab[i = (n ‐ 1) & hash]) == null)
21 tab[i] = newNode(hash, key, value, null);
22 // 桶中已经存在元素
23 else {
24 Node<K,V> e; K k;
25 // 步骤③:节点key存在,直接覆盖value
26 // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
27 if (p.hash == hash &&
28 ((k = p.key) == key || (key != null && key.equals(k))))
29 // 将第一个元素赋值给e,用e来记录
30 e = p;
31 // 步骤④:判断该链为红黑树
32 // hash值不相等,即key不相等;为红黑树结点
33 // 如果当前元素类型为TreeNode,表示为红黑树,putTreeVal返回待存放的node, e可
能为null
34 else if (p instanceof TreeNode)
35 // 放入树中
36 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
37 // 步骤⑤:该链为链表
38 // 为链表结点
39 else {
40 // 在链表最末插入结点
41 for (int binCount = 0; ; ++binCount) {
42 // 到达链表的尾部
43
44 //判断该链表尾部指针是不是空的
45 if ((e = p.next) == null) {
46 // 在尾部插入新结点
47 p.next = newNode(hash, key, value, null);
48 //判断链表的长度是否达到转化红黑树的临界值,临界值为8
49 if (binCount >= TREEIFY_THRESHOLD ‐ 1) // ‐1 for 1st
50 //链表结构转树形结构
51 treeifyBin(tab, hash);
52 // 跳出循环
53 break;
54 }
55 // 判断链表中结点的key值与插入的元素的key值是否相等
56 if (e.hash == hash &&
57 ((k = e.key) == key || (key != null && key.equals(k))))
58 // 相等,跳出循环
59 break;
60 // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
61 p = e;
62 }
63 }
64 //判断当前的key已经存在的情况下,再来一个相同的hash值、key值时,返回新来的val
ue这个值
65 if (e != null) {
66 // 记录e的value
67 V oldValue = e.value;
68 // onlyIfAbsent为false或者旧值为null
69 if (!onlyIfAbsent || oldValue == null)
70 //用新值替换旧值
71 e.value = value;
72 // 访问后回调
73 afterNodeAccess(e);
74 // 返回旧值
75 return oldValue;
76 }
77 }
78 // 结构性修改
79 ++modCount;
80 // 步骤⑥:超过最大容量就扩容
81 // 实际大小大于阈值则扩容
82 if (++size > threshold)
83 resize();
84 // 插入后回调
85 afterNodeInsertion(evict);
86 return null;
87 }

①. キーと値のペアの配列 table[i] が空か null かを判断し、そうでない場合は、resize() を実行して展開します。

②. キー値 key に従ってハッシュ値を計算し、挿入された配列インデックス i を取得します。 table[i]==null の場合は、直接新しいノードを作成して追加します。 table[i] が空でない場合は、⑥に進みます。 ③に移ります。

③. table[i] の最初の要素がキーと同じかどうかを判断し、同じであれば値を直接上書きし、そうでない場合は次のようにします。

④、ここでの同じは hashCode と等しいものを指します。

④. table[i]がtreeNodeであるかどうか、つまりtable[i]が赤黒ツリーであるかどうかを判定し、赤黒ツリーであればキーと値のペアをツリーに直接挿入し、それ以外の場合は⑤に進みます。 ;

⑤. table[i]を走査し、リンクリストの長さが8より大きいかどうかを判断し、8より大きい場合はリンクリストを赤黒ツリーに変換し、赤黒ツリーに挿入操作を実行します。 、それ以外の場合は、リンク リストの挿入操作を実行します。トラバーサル プロセス中にキーがすでに存在していることが判明した場合は、値を直接オーバーライドするだけです。

⑥. 挿入成功後、実際のキーと値のペアのサイズが大容量の閾値を超えているかどうかを確認し、超えている場合は容量を拡張します。

HashMapの拡張演算はどのように実現されているのでしょうか?

①. jdk1.8 では、ハッシュマップ内のキーと値のペアがしきい値を超えた場合、または初期化された場合に、resize メソッドを呼び出して容量を拡張します。

②. 拡張するたびに 2 倍に拡張されます。

③. 展開後、Node オブジェクトの位置は元の位置にあるか、元のオフセットの 2 倍の位置に移動します。putVal() では、この関数内でsize() メソッドが2回使用されていることがわかります。resize() メソッドは、初めて初期化されるとき、または配列の実際のサイズが大きいときに展開されることを示しています。その臨界値 (最初は 12) よりも大きい, この時点で, バケット上の要素は拡張中に再配布されます. これは JDK1.8 バージョンの最適化でもあります. 1.7 では, 後で再配布する必要があります拡張ではハッシュ値を計算し、そのハッシュ値に従って配布しますが、バージョン1.8では、同じバケット位置で(e.hash & oldCap)が0であるかどうかで判断し、再ハッシュ配布後、要素を位置は元の位置に留まるか、元の位置 + 増加した配列サイズの位置に移動します。

1 final Node<K,V>[] resize() {
2 Node<K,V>[] oldTab = table;//oldTab指向hash桶数组
3 int oldCap = (oldTab == null) ? 0 : oldTab.length;
4 int oldThr = threshold;
5 int newCap, newThr = 0;
6 if (oldCap > 0) {//如果oldCap不为空的话,就是hash桶数组不为空
7 if (oldCap >= MAXIMUM_CAPACITY) {//如果大于最大容量了,就赋值为整数最大的阀
值
8 threshold = Integer.MAX_VALUE;
9 return oldTab;//返回
10 }//如果当前hash桶数组的长度在扩容后仍然小于最大容量 并且oldCap大于默认值16
11 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
12 oldCap >= DEFAULT_INITIAL_CAPACITY)
13 newThr = oldThr << 1; // double threshold 双倍扩容阀值threshold
14 }
15 // 旧的容量为0,但threshold大于零,代表有参构造有cap传入,threshold已经被初
始化成最小2的n次幂
16 // 直接将该值赋给新的容量
17 else if (oldThr > 0) // initial capacity was placed in threshold
18 newCap = oldThr;
19 // 无参构造创建的map,给出默认容量和threshold 16, 16*0.75
20 else { // zero initial threshold signifies using defaults
21 newCap = DEFAULT_INITIAL_CAPACITY;
22 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
23 }
24 // 新的threshold = 新的cap * 0.75
25 if (newThr == 0) {
26 float ft = (float)newCap * loadFactor;
27 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
28 (int)ft : Integer.MAX_VALUE);
29 }
30 threshold = newThr;
31 // 计算出新的数组长度后赋给当前成员变量table
32 @SuppressWarnings({"rawtypes","unchecked"})
33 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//新建hash桶数组
34 table = newTab;//将新数组的值复制给旧的hash桶数组
35 // 如果原先的数组没有初始化,那么resize的初始化工作到此结束,否则进入扩容元素
重排逻辑,使其均匀的分散
36 if (oldTab != null) {
37 // 遍历新数组的所有桶下标
38 for (int j = 0; j < oldCap; ++j) {
39 Node<K,V> e;
40 if ((e = oldTab[j]) != null) {
41 // 旧数组的桶下标赋给临时变量e,并且解除旧数组中的引用,否则就数组无法被GC回收
42 oldTab[j] = null;
43 // 如果e.next==null,代表桶中就一个元素,不存在链表或者红黑树
44 if (e.next == null)
45 // 用同样的hash映射算法把该元素加入新的数组
46 newTab[e.hash & (newCap ‐ 1)] = e;
47 // 如果e是TreeNode并且e.next!=null,那么处理树中元素的重排
48 else if (e instanceof TreeNode)
49 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
50 // e是链表的头并且e.next!=null,那么处理链表中元素重排
51 else { // preserve order
52 // loHead,loTail 代表扩容后不用变换下标,见注1
53 Node<K,V> loHead = null, loTail = null;
54 // hiHead,hiTail 代表扩容后变换下标,见注1
55 Node<K,V> hiHead = null, hiTail = null;
56 Node<K,V> next;
57 // 遍历链表
58 do {
59 next = e.next;
60 if ((e.hash & oldCap) == 0) {
61 if (loTail == null)
62 // 初始化head指向链表当前元素e,e不一定是链表的第一个元素,初始化后loHead
63 // 代表下标保持不变的链表的头元素
64 loHead = e;
65 else
66 // loTail.next指向当前e
67 loTail.next = e;
68 // loTail指向当前的元素e
69 // 初始化后,loTail和loHead指向相同的内存,所以当loTail.next指向下一个元素
时,
70 // 底层数组中的元素的next引用也相应发生变化,造成lowHead.next.next.....
71 // 跟随loTail同步,使得lowHead可以链接到所有属于该链表的元素。
72 loTail = e;
73 }
74 else {
75 if (hiTail == null)
76 // 初始化head指向链表当前元素e, 初始化后hiHead代表下标更改的链表头元素
77 hiHead = e;
78 else
79 hiTail.next = e;
80 hiTail = e;
81 }
82 } while ((e = next) != null);
83 // 遍历结束, 将tail指向null,并把链表头放入新数组的相应下标,形成新的映射。
84 if (loTail != null) {
85 loTail.next = null;
86 newTab[j] = loHead;
87 }
88 if (hiTail != null) {
89 hiTail.next = null;
90 newTab[j + oldCap] = hiHead;
91 }
92 }
93 }
94 }
95 }
96 return newTab;
97 }

HashMap はハッシュの競合をどのように解決しますか?

回答: この問題を解決する前に、まずハッシュの衝突とは何かを知る必要があり、ハッシュの衝突を理解する前に、ハッシュとは何か、ハッシュとは何なのかを知る必要があります。

ハッシュは、一般に「ハッシュ」と訳されますが、直接「ハッシュ」とも音訳され、ハッシュ アルゴリズムを通じて任意の長さの入力を固定長の出力に変換し、その出力がハッシュ値 (ハッシュ値) になります。変換は圧縮マッピングです。つまり、ハッシュ値の空間は通常、入力の空間よりもはるかに小さく、異なる入力が同じ出力にハッシュされる可能性があるため、ハッシュ値から入力値を一意に決定することは不可能です。簡単に言うと、任意の長さのメッセージを固定長のメッセージダイジェストに圧縮する機能です。

すべてのハッシュ関数には次の基本特性があります**: 同じハッシュ関数に従って計算されたハッシュ値が異なる場合、入力値も異なる必要があります。ただし、同じハッシュ関数から計算されるハッシュ値が同じであれば、入力値が同じであるとは限りません**。

ハッシュ衝突とは何ですか?

2つの異なる入力値が同じハッシュ関数に従って同じハッシュ値を計算する場合、それを衝突(ハッシュ衝突)と呼びます。

ハッシュマップのデータ構造

Java には、データを格納するための 2 つの比較的単純なデータ構造、配列とリンク リストがあります。配列の特性は、アドレス指定は簡単だが、挿入と削除は難しい、リンク リストの特性は、アドレス指定は難しいが、挿入と削除は簡単であるため、配列とリンク リストを組み合わせて、それぞれの利点を活かします。チェーンアドレスメソッドと呼ばれるメソッドは、ハッシュの競合を解決できます。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-BnuhMgra-1692508006554) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-)キーポイント.assets/image- 20201109163448750.png)]

このようにして、同じハッシュ値を持つオブジェクト (img) をリンクされたリストに整理し、そのハッシュ値に対応するバケットの下に配置できますが、hashCode によって返される int 型と比較すると、HashMap の初期容量は DEFAULT_INITIAL_CAPACITY になります。 = 1 << 4 (つまり、2 の 4 乗 16) は int 型の範囲よりもはるかに小さいため、単に hashCode の残りを使用して対応するバケットを取得すると、次の確率が大幅に増加します。ハッシュの衝突が発生し、最悪の場合、HashMap が単一リンク リストになってしまうため、hashCode hash() 関数も最適化する必要があります。

上記の問題の主な理由は、hashCode を使用して剰余を取得する場合、それは計算に参加する hashCode の下位ビットのみと同等であり、上位ビットは影響を及ぼさないためです。 hashCode 値のビットも計算に参加し、ハッシュ衝突の可能性をさらに減らし、データの分散をより均一にします。このような操作を妨害と呼びます。JDK 1.8 の hash() 関数は次のとおりです。

1	static final int hash(Object key) {
2	int h;
3	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与自己右移16位进行异或运算(高低位异或)
4	}

これは、JDK 1.7 よりも簡潔です。1.7 では 4 ビット演算、5 つの XOR 演算 (9 摂動)、1.8 では 1 ビット演算と 1 つの XOR 演算 (2 摂動) だけであったのに比べて、JDK 1.7 よりも簡潔です。

JDK1.8では赤黒ツリーを追加

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-xF4yqZAB-1692508006555) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-)キーポイント.assets/image- 20201109163526813.png)]

上記のチェーン アドレス法 (ハッシュ テーブルを使用) と摂動 (img) 関数により、データの分散をより均一にし、ハッシュの衝突を減らすことに成功しました。ただし、HashMap に大量のデータがある場合は、それをバケットの 1 つに追加します 対応するリンク リストには n 個の要素があるため、走査時間の複雑さは O(n) です この問題を解決するために、JDK1.8 は HashMap に赤黒ツリー データ構造を追加します。 O(n) logn までの走査複雑さ); 概要

ハッシュの競合を効果的に解決するために HashMap がどのような方法を使用するかを簡単に要約します。

  1. チェーンアドレス方式(ハッシュテーブルを使用)を使用して、同じハッシュ値を持つデータを結合します。
  2. 2 つの摂動関数 (ハッシュ関数) を使用して、ハッシュ衝突の確率を減らし、データの分布をより均一にします。
  3. 赤黒ツリーの導入により、トラバースの時間の複雑さがさらに軽減され、トラバースが高速化されます。

任意のクラスをマップのキーとして使用できますか?

Map のキーとして任意のクラスを使用できますが、使用する前に次の点を考慮する必要があります。クラスが equals() メソッドをオーバーライドする場合は、hashCode() メソッドもオーバーライドする必要があります。クラスのすべてのインスタンスは、equals() および hashCode() に関連するルールに従う必要があります。

クラスがequals()を使用しない場合は、hashCode()で使用しないでください。

ユーザー定義の Key クラスのベスト プラクティスは、パフォーマンスを向上させるために hashCode() 値をキャッシュできるように、キー クラスを不変にすることです。不変クラスは、hashCode() とquals() が将来変更されないことを保証することもでき、これにより、可変に関連する問題が解決されます。

String や Integer などのラッパー クラスが HashMap の K として適しているのはなぜですか?

回答: String や Integer などのパッケージ化クラスの特性により、ハッシュ値の不変性と計算精度が保証され、ハッシュ衝突の確率を効果的に低減できます。

\1. これらはすべて最終型、つまり不変性であり、キーの不変性が保証されており、異なるハッシュ値が取得されることはありません。

equals()、hashCode()、その他のメソッドは、HashMap の内部仕様に準拠して内部的に書き直されており (よくわからない場合は、上記にアクセスして putValue の処理を​​確認できます)、簡単ではありません。ハッシュ値の計算を間違える

HashMap のキーとして Object を使用する場合はどうすればよいですか?

回答: hashCode() メソッドとquals() メソッドを書き換えます。

\1. 格納されたデータの格納場所を計算する必要があるため、hashCode() を書き換えます。パフォーマンスを向上させるために、オブジェクトの重要な部分をハッシュ コードの計算から除外しようとしないように注意する必要があります。これは可能ですが、高速化すると、より多くのハッシュ衝突が発生する可能性があります。

\2.quals() メソッドを書き直すには、再帰性、対称性、推移性、一貫性、および

null 以外の参照値 x の場合、ハッシュ テーブル内のキーの一意性を保証するために、x.equals(null) は false を返す必要があります。

HashMap は、hashCode() で処理されたハッシュ値をテーブルの添え字として直接使用しないのはなぜですか?

回答: hashCode() メソッドは int 整数型を返します。その範囲は -(2 ^ 31) ~ (2 ^ 31 - 1)、マッピング スペースは約 40 億、HashMap の容量範囲は 16 (初期デフォルト) です。 value) ~ 2 ^ 30、HashMap は通常、大きな値を取得できず、デバイス上に多くのストレージ領域を提供することが難しいため、hashCode() で計算されたハッシュ値が配列サイズの範囲内に収まらない可能性があります。 、保存場所が一致しません。

どうやって解決すればいいでしょうか?

\1. HashMap は独自の hash() メソッドを実装しています。2 つの摂動を通じて、独自のハッシュ値の上位ビットと下位ビットを XOR 演算して、ハッシュ衝突の可能性を減らし、データ分布をより均一にすることができます。

\2. 配列の長さが 2 の累乗であることが保証されている場合は、hash() 演算の後に値の AND 演算を使用します。

(&) (配列の長さ - 1) は、ストレージ用の配列添字を取得するため、フェッチするよりも優れています。

残りの演算はより効率的であり、第 2 に、配列の長さが 2 のべき乗である場合にのみ、h&

(length-1) は h%length に相当し、「ハッシュ値が配列のサイズ範囲と一致しない」という問題を解決するための 3 です。

HashMap の長さが 2 の累乗になるのはなぜですか

HashMap へのアクセスを効率的にするには、衝突をできるだけ少なくする必要があります。つまり、データができるだけ均等に分散され、各リンク リスト/赤黒ツリーの長さがほぼ同じである必要があります。この実装は、データをどのリンク リスト/赤黒ツリーに格納するかのアルゴリズムです。

このアルゴリズムはどのように設計すべきでしょうか? まず、それを達成するために余りを取る操作を使用することを考えるかもしれません。ただし、ここで重要なのは「余り(%)を計算する」ということです。

演算では、除数が 2 の累乗の場合、除数から 1 を引いた AND (&) 演算と同等になります (つまり、

hash%length==hash&(length-1) の前提は、長さが 2 の n 乗であることです;)。" また、バイナリ ビット演算 & を使用すると、% と比較して演算効率が向上します。これが、HashMap の長さが 2 の累乗である理由の説明になります。

では、なぜ 2 つの摂動があるのでしょうか? 回答: これは、ハッシュ値の下位ビットのランダム性を高めて分布をより均一にし、それによって対応する配列ストレージの添字位置のランダム性と均一性を改善し、最終的にハッシュの競合を減らすためです。同時に操作に参加する目的

ハッシュマップとハッシュテーブルの違いは何ですか?

  1. スレッド セーフ: HashMap は非スレッド セーフですが、HashTable はスレッド セーフです。

HashTable 内のメソッドは基本的に synchronized によって変更されます。(スレッドの安全性を確保したい場合は、ConcurrentHashMap を使用してください!);

  1. 効率: スレッドの安全性の問題により、HashMap は HashTable よりも若干効率的です。また、HashTable は基本的に削除されているため、コード内で使用しないでください。

  2. Null キーと Null 値のサポート: HashMap では、null をキーとして使用できます。そのようなキーは 1 つだけあり、対応する値が null であるキーは 1 つ以上存在する可能性があります。でもいつ

HashTable の put キーに null 値がある限り、それは直接スローされます。

NullPointerException。

  1. **初期容量サイズと各拡張の容量サイズの違い**: ①作成時に初期容量値を指定しない場合、Hashtableのデフォルトの初期サイズは11であり、各拡張後の容量は元の2nになります+1。HashMap のデフォルトの初期化サイズは 16 です。拡張するたびに、容量は元の 2 倍になります。②作成時に容量の初期値を指定すると、Hashtableは指定したサイズをそのまま使用し、HashMapは2のべき乗のサイズに拡張します。つまり、HashMap ではハッシュ テーブルのサイズとして常に 2 の累乗が使用されます。なぜ 2 の累乗なのかについては後で紹介します。

  2. 基礎となるデータ構造: JDK1.8 以降の HashMap は、ハッシュの競合を解決するために大幅な変更を受けています。リンク リストの長さがしきい値 (デフォルトでは 8) より大きい場合、リンク リストは赤黒ツリーに変換され、検索時間。Hashtable にはそのようなメカニズムはありません。

  3. 推奨される使用法: Hashtable のクラス コメントにあるように、Hashtable は予約クラスであるため、使用することは推奨されません。シングルスレッド環境では HashMap を使用し、マルチスレッド環境では ConcurrentHashMap を使用することをお勧めします。が必要です。

HashMap と TreeMap のどちらを使用するかをどのように決定すればよいですか?

HashMap は、マップ内の要素の挿入、削除、検索などの操作に適しています。しかし

ただし、順序付けられたキーのコレクションを走査する必要がある場合は、TreeMap の方が良い選択です。コレクションのサイズに応じて、HashMap に要素を追加し、順序付けされたキーのトラバーサルのためにマップを TreeMap に置き換えた方が速い場合があります。

HashMap と ConcurrentHashMap の違い

  1. ConcurrentHashMap はバケット配列全体をセグメント (Segment) に分割し、各セグメントをロックすることで各セグメントを保護します。HashTable の同期ロックに比べて粒度が細かく同時実行性が優れていますが、HashMap にはロック機構がありません。スレッドセーフではありません。(JDK1.8 以降、ConcurrentHashMap は CAS アルゴリズムを使用した新しい実装方法を開始しました。)

  2. HashMap のキーと値のペアでは null が許可されますが、ConCurrentHashMap ではそれが許可されません。

ConcurrentHashMap と Hashtable の違いは何ですか?

ConcurrentHashMap と Hashtable の違いは、主にスレッド セーフを実現する方法に反映されます。

基礎となるデータ構造: JDK1.7 の ConcurrentHashMap は下部にセグメント化された配列を使用します

+リンクリスト実装、JDK1.8で採用されているデータ構造はHashMap1.8と同じ、配列+リンクリスト/赤と黒

二分木。JDK1.8以前のHashtableとHashMapの基本的なデータ構造は、配列+リンクリストの形式に似ており、配列はHashMapの本体であり、リンクリストは主にハッシュの競合を解決するために存在します。

スレッドセーフを実現する方法(重要): ① JDK1.7では、

ConcurrentHashMap (セグメント ロック) はバケット配列全体をセグメント (セグメント) に分割し、各ロックはコンテナ内のデータの一部のみをロックします。コンテナ内の異なるデータ セグメント内のデータへのマルチスレッド アクセスにより、ロックの競合が防止され、同時実行アクセスが向上します。レート。(デフォルトでは、16 セグメントが割り当てられます。これは、Hashtable よりも 16 効率的です。

回。) JDK1.8の時点ではセグメントの概念は放棄されていますが、直接使用されています。

ノード配列+リンクリスト+赤黒ツリーのデータ構造を実現し、同時実行制御を採用

同期され、CAS が動作します。(JDK1.6 では同期ロックに対して多くの最適化が行われています) まだ JDK1.8 の段階ではありますが、全体としては最適化されたスレッドセーフな HashMap のように見えます。

Segment のデータ構造がわかりますが、属性は旧バージョンとの互換性を考慮して簡略化されています。②

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

2 つの比較表:

ハッシュ表:

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-nGpDytvk-1692508006555) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-)キーポイント.assets/image- 20201109165808848.png)]

JDK1.7の同時ハッシュマップ:

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-EcYg9xdw-1692508006555) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-) focus.assets/image- 20201109165820466.png)]

JDK1.8のConcurrentHashMap (TreeBi(img)n: 赤と黒の二分木ノード ノード: リンクリストノード):

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-RC4PO3Fy-1692508006556) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-)キーポイント.assets/image- 20201109165840129.png)]

回答: ConcurrentHashMap は、Hash(img)Map と HashTable の利点を組み合わせています。HashMap は同期を考慮しませんが、HashTable は同期を考慮します。ただし、HashTable は同期的に実行されるたびに構造全体をロックします。ConcurrentHashMap のロック方法は少し細かく設定されています。

ConcurrentHashMap の基礎となる実装をご存知ですか? 実現原理とは何ですか?

JDK1.7

まず、データは保存のためにセクションに分割され、次にデータの各セクションにロックが割り当てられます。スレッドがデータの 1 つのセクションにアクセスするためにロックを占有すると、データの他のセクションには他のスレッドからもアクセスできます。

JDK1.7では、ConcurrentHashMapはSegment + HashEntryを使用して実装します

さて、構造は次のとおりです。

ConcurrentHashMap にはセグメント配列が含まれます。セグメントの構造は HashMap に似ています。配列とリンク リスト構造です。セグメントには HashEntry 配列が含まれます。各 HashEntry はリンク リスト構造の要素です。各セグメントは HashEntry 配列の要素を保護します。 HashEntry 配列を変更する場合は、まず対応するセグメント ロックを取得する必要があります。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-K1gdrPKc-1692508006557) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-) focus.assets/image- 20201109165916185.png)]

  1. このクラスには、2 つの静的内部クラス HashE(img)ntry と Segment が含まれています。前者はマッピング テーブルのキーと値のペアをカプセル化するために使用され、後者はロックとして機能するために使用されます。

  2. セグメントは再入可能なロック ReentrantLock です。各セグメントは HashEntry 配列の要素を保護します。HashEntry 配列のデータを変更する場合は、対応するセグメント ロックを最初に取得する必要があります。

JDK1.8

JDK1.8 では、肥大化したセグメント設計が放棄され、同時実行セキュリティを確保するために代わりにノード + CAS + 同期が使用されました。同期は、現在のリンク リストまたは赤黒バイナリ ツリーの最初のノードのみをロックします。競合しないため同時実行性は発生せず、効率は N 倍に向上します。

構造は次のとおりです。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-FCdhnfGk-1692508006557) (02-Java コレクション コンテナのインタビューの質問 (2020 最新版)-)キーポイント.assets/image- 20201109165939912.png)]

要素を挿入するプロセスを確認してください (ソース コードを確認することをお勧めします)。

対応する位置のノードが初期化されていない場合は、CAS を呼び出して対応するデータを挿入します。

1	else if ((f = tabAt(tab, i = (n ‐ 1) & hash)) == null) {
2	if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
3	break; // no lock when adding to empty bin
4	}

対応する位置のノードが空ではなく、現在のノードが移動状態にない場合は、次の値を追加します。

同期ロック。ノードのハッシュが 0 以上の場合、リンクされたリストを走査してノードを更新するか、新しいノードを挿入します。

1 if (fh >= 0) { 
2 binCount = 1;
3	for (Node<K,V> e = f;; ++binCount) {
4	K ek;
5	if (e.hash == hash &&
6	((ek = e.key) == key ||
7	(ek != null && key.equals(ek)))) {
8	oldVal = e.val;
9	if (!onlyIfAbsent)
10	e.val = value;
11	break;
12	}
13	Node<K,V> pred = e;
14	if ((e = e.next) == null) {
15	pred.next = new Node<K,V>(hash, key, value, null);
16	break;
17	}
18	}
19	}

\1. ノードが TreeBin タイプのノードであり、赤黒ツリー構造であることを示している場合は、putTreeVal メソッドを通じてノードを赤黒ツリーに挿入します。binCount が 0 でない場合は、put が存在することを意味します。操作がデータに影響を与えた場合、現在のリンク リストの数値が 8 に達すると、treeifyBin メソッドによって赤黒ツリーに変換されます。oldVal が空でない場合は、影響を与えない更新操作であることを意味します。要素の数と古い値が直接返されます。

\2. 新しいノードが挿入された場合は、addCount() メソッドを実行して要素数の更新を試みます。

補助ツール

Array と ArrayList の違いは何ですか?

  • Array は基本的なデータ型とオブジェクトを格納できますが、ArrayList はオブジェクトのみを格納できます。
  • 配列は固定サイズで指定されますが、ArrayList のサイズは自動的に拡張されます。
  • Array には、ArrayList ほど多くの組み込みメソッドはありません (addAll、removeAll、iteration、および ArrayList にのみ存在するその他のメソッドなど)。

プリミティブ型の場合、コレクションはオートボックス化を使用してコーディングの労力を軽減します。ただし、固定サイズのプリミティブ データ型を扱う場合、このアプローチは比較的遅くなります。

配列とリストの間の変換を実現するにはどうすればよいですか?

  • 配列转リスト: 配列。asList(配列) ;
  • リストから配列へ: リストの toArray() メソッド。

比較対象とコンパレータの違いは何ですか?

  • 実際、同等のインターフェイスは java.lang パッケージから来ており、このパッケージには並べ替え用の CompareTo(Object obj) メソッドがあります。
  • コンパレータ インターフェイスは実際には java.util パッケージからのもので、ソート用の Compare(Object obj1, Object obj2) メソッドがあります。

一般に、コレクションに対してカスタムの並べ替えを使用する必要がある場合は、compareTo メソッドまたは Compare メソッドを書き直す必要があります。特定のコレクションに対して 2 つの並べ替えメソッドを実装する必要がある場合 (たとえば、曲の曲名と歌手名)。オブジェクトの使用 a 並べ替えメソッドが 1 つである場合、compareTo メソッドを書き換えて自作の Comparator メソッドを使用するか、2 つの Comparator を使用して曲名と歌手名を並べ替えることができます。2 つ目は、2 つのパラメーターのみを使用できることを意味します。 Collections.sort() のバージョン。

Collection と Collections はどう違いますか?

  • java.util.Collection は、コレクション インターフェイス (コレクション クラスの最上位インターフェイス) です。これは、コレクション オブジェクトに対する基本的な操作のための共通のインターフェイス メソッドを提供します。Collection インターフェイスには、Java クラス ライブラリに多くの具体的な実装があります。Collection インターフェイスの重要性は、さまざまな特定のコレクションに対して最大化された統一操作メソッドを提供することであり、その直接継承インターフェイスには List と Set が含まれます。
  • Collections は、コレクション クラスのツール クラス/ヘルパー クラスであり、コレクション内の要素に対する並べ替え、検索、およびスレッド セーフな操作のための一連の静的メソッドを提供します。

TreeMap と TreeSet はソート時に要素をどのように比較しますか? コレクション ツール クラスの sort() メソッドはどのように要素を比較しますか?

TreeSet では、格納されたオブジェクトのクラスが要素を比較するための CompareTo() メソッドを提供する Comparable インターフェイスを実装する必要があります。要素が挿入されると、このメソッドは要素のサイズを比較するためにコールバックされます。

TreeMap では、要素をキーに従ってソートできるように、保存されたキーと値のペアのマッピング キーが Comparable インターフェイスを実装する必要があります。

コレクション ツール クラスの sort メソッドには 2 つのオーバーロードされた形式があります。

最初のタイプでは、コンテナーに格納されているソート対象のオブジェクトが要素を比較するための Comparable インターフェイスを実装する必要があります。

  • リストから配列へ: リストの toArray() メソッド。

比較対象とコンパレータの違いは何ですか?

  • 実際、同等のインターフェイスは java.lang パッケージから来ており、このパッケージには並べ替え用の CompareTo(Object obj) メソッドがあります。
  • コンパレータ インターフェイスは実際には java.util パッケージからのもので、ソート用の Compare(Object obj1, Object obj2) メソッドがあります。

一般に、コレクションに対してカスタムの並べ替えを使用する必要がある場合は、compareTo メソッドまたは Compare メソッドを書き直す必要があります。特定のコレクションに対して 2 つの並べ替えメソッドを実装する必要がある場合 (たとえば、曲の曲名と歌手名)。オブジェクトの使用 a 並べ替えメソッドが 1 つである場合、compareTo メソッドを書き換えて自作の Comparator メソッドを使用するか、2 つの Comparator を使用して曲名と歌手名を並べ替えることができます。2 つ目は、2 つのパラメーターのみを使用できることを意味します。 Collections.sort() のバージョン。

Collection と Collections はどう違いますか?

  • java.util.Collection は、コレクション インターフェイス (コレクション クラスの最上位インターフェイス) です。これは、コレクション オブジェクトに対する基本的な操作のための共通のインターフェイス メソッドを提供します。Collection インターフェイスには、Java クラス ライブラリに多くの具体的な実装があります。Collection インターフェイスの重要性は、さまざまな特定のコレクションに対して最大化された統一操作メソッドを提供することであり、その直接継承インターフェイスには List と Set が含まれます。
  • Collections は、コレクション クラスのツール クラス/ヘルパー クラスであり、コレクション内の要素に対する並べ替え、検索、およびスレッド セーフな操作のための一連の静的メソッドを提供します。

TreeMap と TreeSet はソート時に要素をどのように比較しますか? コレクション ツール クラスの sort() メソッドはどのように要素を比較しますか?

TreeSet では、格納されたオブジェクトのクラスが要素を比較するための CompareTo() メソッドを提供する Comparable インターフェイスを実装する必要があります。要素が挿入されると、このメソッドは要素のサイズを比較するためにコールバックされます。

TreeMap では、要素をキーに従ってソートできるように、保存されたキーと値のペアのマッピング キーが Comparable インターフェイスを実装する必要があります。

コレクション ツール クラスの sort メソッドには 2 つのオーバーロードされた形式があります。

最初のタイプでは、コンテナーに格納されているソート対象のオブジェクトが要素を比較するための Comparable インターフェイスを実装する必要があります。

2 番目のタイプでは、コンテナ内の要素が比較できる必要はありませんが、Comparator インターフェイスのサブタイプである 2 番目のパラメータを渡す必要があります (要素の比較を行うには比較メソッドをオーバーライドする必要があります)。一時的に定義したものに相当 ソートルールは、実際にはインターフェイスインジェクションを通じて要素のサイズを比較するアルゴリズムであり、コールバックモード(Javaの関数型プログラミングのサポート)の応用でもあります。

おすすめ

転載: blog.csdn.net/leader_song/article/details/132390992