Java コレクションのリスト、セット、マップの基礎となるソース コード分析

コレクション紹介

コレクションを使用する理由
データのセットを保存する場合、使用できる変数の型はコレクションと配列です。配列の制限について次のように言います。

  1. 配列の型が決定されると、配列全体にはこの型のデータのみを配置できます。もちろん、オブジェクト型として指定することもできます。
  2. 配列の長さは固定されており、データがあらかじめ設定されたサイズを超えるとエラーが発生します。一部のコレクション型では下部に配列も使用されますが、コレクションは配列を自動的に管理し、自動的に展開してコピーします。
  3. 配列は選択性が低く、データの使用状況に応じて適切なデータ構造を選択することができません。配列はその一種であり、配列のデータ構造の特徴は連続したアドレス空間であること、数组数据结构通し番号から各要素のメモリアドレスを高速に計算できること、データの挿入や削除には配列を使用できないことである。コレクションにはさまざまなタイプがあります。たとえば、linkedList は二重リンク リストです。選択できる配列のタイプは 1 つだけであり、コレクションはデータの頻繁な削除と追加のためにリンク リスト タイプの構造を選択できます。中间插入数据删除数据

配列は比較的原始的ですが、コレクション関数はより豊富です。コレクションによっては最下層に配列があり、これは配列に基づいた一部の関数の拡張です。配列はデータ型の 1 つにすぎません。コレクションでは、リンク リスト、配列、ハッシュ テーブル、赤黒ツリーなど、さらに多くのデータ型を使用できます。

コレクションは単一列コレクションと二重列コレクションに分けられ、単一値コレクションは単一列コレクション、K_V コレクションは二重列コレクションです。単一列のコレクションは List と Set の 2 つのカテゴリに分けられ、2 列のコレクションは Map (k_V キーと値のペア モード、K は繰り返し不可) のみです。各コレクションの
機能が異なるのはなぜですか
? それは、基礎となるデータ構造が異なるか、拡張メカニズムが異なるか、セキュリティ メカニズムが異なるかのいずれかであるためです。

1 つのタイプのコレクションは、さまざまな要件に応じて多くのタイプに分割されます。ここでは、一般的に使用されるいくつかのタイプのみを紹介します。なお、以下の図はクラスとクラスの親子関係を示すものではなく、分類するためのものです
ここに画像の説明を挿入
ソースコードを読んでいると、ソースコードはループ時に値を判断して代入するのが非常に好きだという特徴を発見しました

地図

ハッシュマップ

K_V キーと値のペアのセットを追加する場合のHashMap の構築方法
ここに画像の説明を挿入

まず、HashMao の基礎となる層がどのように格納されるかを理解します
。key_value キーと値のペアのセットを入力するとき、キーは最初にハッシュされ、次に Node<k_v> に格納される値は、ハッシュ操作の結果を で割ることによって計算されます。基礎となる Node<k_v> 配列の長さ。位置、この位置にすでに値がある場合、その値は逆方向に格納され、Node<k_v> 配列内の同じ位置にあるノードが単一のリンク リスト構造を形成します。 。リンク リストの数が を超える場合は8、Node<k_v> 配列を展開します。展開された配列の長さが 64 に達すると、リンク リストは树化に変換されます红黑树
ここに画像の説明を挿入
以下にこの処理を詳しく紹介します。
まずキーがnullかどうかを判定し、nullであれば値は0、nullでなければそのハッシュコードを取得して右に16ビット移動します。それぞれの最
ここに画像の説明を挿入
下層k_v キーと値のペアのグループは Node<k_v > に格納され、Node<k_v> はMap.Entry<k_v>
ここに画像の説明を挿入
の特定のストアド プロシージャを継承します。
ここに画像の説明を挿入

HashMap の最下層は table という名前の配列で、計算された配列座標に既に値がある場合は、リンク リストの形式で逆方向に格納されます。自分のハッシュ値ページと同じキーを見つけた場合は、古い値を取り出して新しい値で上書きします。この期間中、2 つの拡張があり、1 つはテーブル配列の長さが 0 の場合、もう 1 つはテーブル配列の長さの場合 (各加算サイズは ++ になるため、リンク リストの長さがカウントされます) ) 警告値に達しました。では、拡大のプロセスとはどのようなものなのでしょうか?これはresize()メソッドを確認する必要があります.
ここに画像の説明を挿入
上記は、テーブル テーブルの総数がしきい値を超えた場合、または空のテーブル配列に初めて新しい値が入れられた場合のテーブル配列の拡張です。リストが 8 を超え、配列が 64 未満の場合、拡張も実行されますが、tableなぜHashMap
ここに画像の説明を挿入
ここに画像の説明を挿入
配列 + リンク リスト + 赤黒ツリーという保存方法を使用するのでしょうか? 配列は高速な検索と制限された長さによって特徴付けられますが、ハッシュを使用して配列の添え字を決定するとハッシュの衝突が起こりやすいため、リンクされたリストを使用する必要があります。キーに基づいて値を検索する場合、配列の特定の位置をすばやく特定できるように、最初にキーに対してハッシュ計算が実行されます。ただし、この場所には複数のノードが存在する可能性があるため、リンクされたリストに沿ってキーを順番に比較する必要があります。ただし、リンク リストが長すぎると、リンク リストの検索に時間がかかりすぎます。したがって、リンク リストの数が 8 に達した場合は、配列を拡張してハッシュ衝突の可能性を減らすか、リンク リストをツリー化してキー検索の効率を向上させます。

hashMap はキーに基づいてどのように要素を見つけますか?
ここに画像の説明を挿入
思考: 見つけたいノードであるかどうかを判断したらどうなるでしょうか?
これはソース コードで指定されており、ハッシュ値は同じである必要があり (ノードの追加時にノード ノードに入力されています)、キーも同じである必要があります。HashMapのキーはString型だけでなくオブジェクトも使用できます。
ここに画像の説明を挿入
それでは、cat1 オブジェクトが cat2 オブジェクトをカバーするようにするにはどうすればよいでしょうか。あるいは、マップ コレクション内に同じタイプのオブジェクトは 1 つだけ存在できるようにするか、特定の属性に基づいて同じキーであるかどうかのみを判断するのでしょうか。
まず、キーの判定と同じであることを理解しておく必要がありますが、以前にも紹介しましたが、同じハッシュ値を使用してキーが同一か等しいかを比較します。あとは各クラスのHashCodeメソッドとequalsメソッドを書き換えるだけで、同じキーかどうかを自分の考え通りに判断できるようになります。
ここに画像の説明を挿入

HashMap のいくつかのトラバースメソッド
HashMap の挿入とトラバースが順序が狂っていると言われるのはなぜですか? HashMapを再挿入する際、配列に順番に並べるのではなく、キーのハッシュ値を計算して挿入する配列位置を取得するためです。走査するとき、走査は配列の順序で実行されます。その結果、挿入順序は異なりますが、配列またはリンク リスト内で接続され、走査結果の順序が同じになります。
ここに画像の説明を挿入

Map コレクションは、キーを keySet としてまとめ、Value 値を Values として 1 つにまとめ、key_values の各セットを Entry にすることができます。ただし、keySet() メソッドまたは Values() メソッドが呼び出された場合、キーまたは値を個別に保存するのではなく、マップを走査することによって戻り値も取得されることに注意してください。
次に例を示します。 keySet ソース コード
ここに画像の説明を挿入
このソース コードから、KeySet が size()、Clear() メソッドなどを備えた Set コレクションまたはオブジェクトであることがわかります。
KeySet() を使用してキーを取得し、Map を介して走査するキーを検索します。
ここに画像の説明を挿入
ここに画像の説明を挿入

EntrySet() を使用して k_v 値を取得または設定します
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入

リンクされたハッシュマップ

LinkedHashMap継承されましたHashMap类この単連結リストに基づいてNode<k_v>、さらに多くのポインターが存在しますbeforeafterノードは単連結リストの次のノードを指しますが、前に挿入された要素と次に挿入される要素も指します。
ここに画像の説明を挿入
ここに画像の説明を挿入
このようにして、マップの挿入を順番に行うことができます。元の配列とリンク リストに基づいて、最初に挿入されたノードを介してリンク リストに沿って最後のノードを見つけることができます。挿入ルールは引き続き使用されます。 HashMap の場合、Node ノードを同じものに置き換えるだけです

ここに画像の説明を挿入
Hashmap は 2 つの無関係な構造を維持します。1 つはストレージ用のリンク リスト/ツリー、もう 1 つはトラバーサル用のリンク リストで、最初は先頭を指し、最後は末尾を指します。ノードを挿入するときは、元の最後のノードを新しい
ここに画像の説明を挿入
ノードに置き換えます。before ポインタが指すと同時に、自身のノードを tail として割り当て、after元の最後のノードのポインタは、最後に挿入されたノードを指します。このようにして、元の HashMap に基づいて、挿入順序によって単一リンク リストが形成され、挿入が順序よく行われます。
LinkedHashMap が keySet() を実行すると、元の HashMap が直接走査されtable、その後リンク リストが実行されます。次に、順序付けされた LinkedhashMap がどのように横断されるかを確認します。
ここに画像の説明を挿入
LinedHashMap と hashMap の走査
ここに画像の説明を挿入
比較は次のとおりです: LinkedHashMap は HashMap に非常に似ています. LinedHashMap は挿入の順序で形成された複数のリンクされたリストにすぎません. 2 つは走査方法を除いてほぼ同じです.どちらも安全ではありません.

ハッシュ表

テーブル配列はハッシュテーブル構築時に初期化されていますが、
ここに画像の説明を挿入
次にハッシュテーブルの挿入方法を見てみましょう、ハッシュテーブルの展開機構と挿入方法は非常に特殊です。
ここに画像の説明を挿入
ここに画像の説明を挿入

ここに画像の説明を挿入
考える:Hashtableデータを置くだけのHashMap利点は何ですか不同?

  • HashTable はテーブルの構築時の初期サイズを指定します。デフォルトは 11、手動指定は少なくとも 1 です。
  • HashTable は配列の添え字を計算して保存し(hash & 0x7FFFFFFF) % tab.length、hashMap はそれを行います (n - 1) &key.hashCode()) ^ (h >>> 16)
  • HashTable はスレッドセーフであり、put メソッドはsynchronized変更されており、HashMap はスレッドアンセーフです
  • HashTable がデータを挿入する場合、そのデータはリンク リストの前に挿入され、配列の前の空の値、単一のエントリ、またはエントリのリンク リストはすべて、挿入されたノードの後ろに配置されますnext
  • ハッシュテーブルのValue在插入时不能为空。
  • Hashtable は count を使用してノード数を記録し、HashMap は size を使用して記録します。
  • 容量拡張のタイミングは条件により異なります
    ここに画像の説明を挿入

同じ部分:

  • しきい値比率は 0.75 のままですが、構築時に Hashtable を指定できます。

上記のソースコードでは展開されるcount >= thresholdので、 rehash()
ここに画像の説明を挿入
HashMapとは異なる展開のソースコード展開を紹介します

  • ハッシュテーブルの拡張にはデータを再配置する必要があります
  • HashTable にはリンクされたリストの長さに制限がありません

プロパティ

プロパティは HashTable を継承します。HashTable は Hashtable に非常に似ており、構成ファイルを読み取り、hashTable に変換するのにより適しています。

コレクション

Collection は一方向コレクション インターフェイスであり、多くのメソッドがすべての一方向コレクションの共通関数としてインターフェイスに定義されています。
ここに画像の説明を挿入

リスト

配列リスト

ArrayList の最下層は、elementDataという名前の配列です。ArrayList コレクションを作成するとき、パラメーター化された構造とノンパラメトリック構造が提供されます。
直接使用した場合new ArrayList()、これはノンパラメトリック構造です。コンストラクター内では、Object 型の空の配列が次のようになります。 elementData 配列に割り当てられる
ArrayList オブジェクトを作成する引数なしの構築
ここに画像の説明を挿入
ArrayList を構築するときに、 Collection コレクションを渡すと、コレクションはこのコレクションに基づいて変換され、操作されます。
ここに画像の説明を挿入

ArrayList オブジェクトの作成時に初期配列サイズを指定します。
ここに画像の説明を挿入

最初に配列に要素を追加するとき、elementData 配列は 10 に拡張されますが、
ここに画像の説明を挿入
拡張された配列の容量を使い果たした場合はどうすればよいでしょうか? 1.5倍扩容
ここに画像の説明を挿入
拡張後の超出サイズがすでに MAX_ARRAY_SIZE である場合、それは不在采用1.5倍拡張メソッドですが、直接Integer.MAX_VALUE
ここに画像の説明を挿入
ArrayList の拡張メソッドを要約するために戻ります。
ここに画像の説明を挿入
s はデータが格納されるときの配列の座標です。 s が次のサイズに等しい場合配列の場合、新しいデータを格納する場所がないことを意味します。最初は s=0、elementData の長さは 0 であり、データを格納できず、容量を拡張する必要があります。拡張後、 elementData[s]=現在のデータ、次に s++。配列に最後の要素を追加して s++=10 とすると、次の要素は 11 番目のデータということになりますが、このとき s=elementData.length となり、再度容量を拡張する必要があります。

ベクター

パラメータを付けずに Vector オブジェクトを作成します
。Vectorの最下層も使用します名为elementData的数组。配列の初期サイズは ArrayList とは異なります。要素を追加するときに空の配列の拡張であるかどうかを判断します。空の配列の拡張である場合は、次の方法で容量を拡張します。 10 単位。Vector にパラメータのない構築の場合、引数なしの構築ではパラメータ構築を呼び出し、パラメータを 10 に設定します。オブジェクトの作成時に、指定された長さのオブジェクト型の
ここに画像の説明を挿入elementData 配列が作成されます。上記の ArrayList とは異なり、
配列内で最初に要素を追加するときは、ArrayList とは異なり、スレッドセーフです。
Vector を作成するときに、各展開のサイズを指定できます。指定しない場合、デフォルトでは、元の配列サイズに同じサイズが追加されます。基になる配列がいっぱいの場合はどうなりますか
? 2倍扩容
ここに画像の説明を挿入
では、Veactor を構築するときに elementData のサイズを手動で 0 に指定した場合はどうなるでしょうか?
Vector の作成時にデフォルトで 10 個のサイズと長さを指定するのが不適切だと思われる場合は、オブジェクトの作成時に手動で長さ 0 を指定して、elementData の長さが 0 になるようにすることができます。要素を追加しながら展開します
ここに画像の説明を挿入

Vector と ArrayList の違い

  • Vector はスレッドセーフで、 add() メソッドの前に追加されますsynchronizedが、ArrayList はスレッドセーフではありません。
  • Vector は、パラメータ構造を持たないオブジェクトを作成するときに elementData 配列のサイズを広げます。ArrayList がパラメータ構造を持たないオブジェクトを作成するとき、elementData 配列のサイズは空になり、要素を追加するときにのみ初期化されます。
  • Vector では各展開のサイズを指定でき、パラメータ構造体の第 1 パラメータで elementData のサイズを指定し、第 2 パラメータで各展開のサイズを指定します。ArrayListの指定はできず、サイズは都度固定となります。
  • Vector で各展開のサイズを指定しない場合、展開は新数组长度=原数组长度+原数组长度2 倍に相当し、ArrayList は新数组长度=原数组长度+0.5原数组长度1.5 倍に相当します。
  • ArrayList を構築するときに、データのコレクションを割り当てることができます。
    ここに画像の説明を挿入

Vector と ArrayList の類似点

  • 最下層は、という名前のelementData配列を受け取ります。
  • トリガーの拡張メカニズムは同じで、拡張メソッドは s==elementData.length のときに呼び出されます。展開後、新しいデータ配列の添え字として s を使用します。s++
    ここに画像の説明を挿入
    Vector は次のシナリオに適しています
    スレッド セーフが必要な場合は Vector を使用し、各展開のサイズを手動で指定する場合は Vector を使用します。

概要:
ここに画像の説明を挿入

リンクリスト

LinkdList の最下層は、双向链表上の 2 つが配列であることです。リンク リストと配列の違いは、リンク リストは非常に柔軟であることです。要素を追加するには、単一のノードを作成してポインタをポイントするだけで済みますが、配列では事前にスペースを空ける必要があります。 LinkedList の構築方法が ArrayList や Vector の構築方法と異なるため、スペースを空ける必要がありません。
注目すべき点は次のとおりです。Java にはポインターの概念はありませんが、ポインターは実数です。例: String a=new String("test")、a はヒープ領域内の String オブジェクトのアドレスを指します。
ここでは Java を使用して、単純な一方向リンク リストを作成します。
ここに画像の説明を挿入

LinkedListの構築方法
ここに画像の説明を挿入
ですが、LinkedListにデータを追加する場合

ここに画像の説明を挿入
最後に宛先となったノードを記録し、最後に新しく追加されたノードを指します。このノードが最初のノードであるかどうかの判断は、元の最後のポインティング ノードが空かどうかによって決まります。元の最後のポインティング ノードが空の場合、このノードは最初のノードであり、最初のノードが実行されて実行されます。元の last が指すノードが空でない場合は、新しく追加されたノードの前にノードがあることを意味します。これは、新しいノードの作成時に、新しいノードの pred ポインタが last が指す位置を指しているためです。ただし、前のノードの場合、next新しく追加されたノードを指すものはありません。
それでは、要素を削除するときに二重リンクリストはどのように動作するのでしょうか?

ここに画像の説明を挿入

設定

ハッシュセット

HashSet の本質は HashMap です。HashSet の各値は HashMap のキーに対応し、その値は空のオブジェクトに設定されます。
これは、HashSet の 2 つの特性も説明しています。

  1. 繰り返しなし: HashMap のキーが繰り返しを許可していないため
  2. 順序なし: HashMap の最下層は、挿入順序ではなくハッシュ値に従ってテーブル配列 (ノード型) 内のデータの位置を計算するため、順序がなく、挿入順序に従っていないと言われます。 。


次に HashSetのソースコードを紹介しますが、HashMap については以前に紹介したのでここでは繰り返しません、データ
ここに画像の説明を挿入
追加時のテーブルの初期サイズ add()
ここに画像の説明を挿入

リンクされたハッシュセット

LinkedHashSet の最下層も LinkedHashMap ですが、HashMap と比較した LinkedhashMap の特徴は何でしょうか? 最大の特徴はリンクリストを別途保持し、挿入順にHashMapを出力できることです。したがって、LinkedhashSet と HashSet の最大の違いは、LinkedHashMap は順序付けされていますが、要素を追加するときにKey が再現できないことです。
ここに画像の説明を挿入

ここに画像の説明を挿入

ツリーセット

ComparatorTreeSet の最下層は TreeMap を使用します。TreeMap の最大の特徴は、最下層が赤黒のツリーになっており、挿入されたキーをソートできることです。もちろん、カスタマイズしたい場合は、 TreeSet
ここに画像の説明を挿入
も使用するメソッドを渡すことができます値をTreeMapのキーとして設定し、Set Sortで値を設定できます。
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/m0_52889702/article/details/128574966