JavaRush /Java Blog /Random-JA /HashMapクラスの詳細な分析
Vonorim
レベル 26

HashMapクラスの詳細な分析

Random-JA グループに公開済み
クラスの詳細な説明に進む前に、ハッシュ テーブルに関連する基本概念に焦点を当てましょう。この記事では、ハッシュ マッピングを操作する方法については説明しません。挿入、検索、削除の操作のみを明確かつ詳細に説明します。同じシルト氏のHashMapで利用できるメソッドの説明を読むのは難しくないと思います。おそらく将来的には、そのような方法に特化した別の記事を書くことになるでしょうが、今のところこれは疑問です。Java 7 と比較して、Java 8 の HashMap クラスには大幅な変更が加えられています (+1000 行のコード)。Java 7 での実装については、ここで読むことができます (ただし、もう関係ありません): habr ハッシュ テーブルは、連想配列インターフェイス (抽象キーと値のモデルまたはエントリ)を実装するデータ構造であり、非常に高速な挿入と検索を提供します。要素の数の挿入と検索 (場合によっては削除) は、定数 - O(1) に近い時間で実行されます。本質的に、これは通常の配列であり、要素の位置は要素自体の値によって異なります。要素の値とハッシュ テーブル内のその位置の関係は、ハッシュ関数によって指定されます。 ハッシュ関数は、キーと呼ばれる入力データを受け取り、出力としてハッシュ値 (またはハッシュ コード)として知られる整数を生成します。次に、ハッシュ値によってキーが特定のハッシュ テーブル インデックスにバインドされます。基本的な操作 (挿入、検索、削除) には同じハッシュ関数を使用するため、これらの操作は非常に高速に実行されます。このため、ハッシュ関数が一貫して動作し、同じ入力データに対して同じインデックスを出力することが重要です。結果として得られるハッシュ コードは巨大な数値になる可能性があり、元の配列は条件付きで 16 要素のみに設計されていることに注意してください。たった 10 個を追加するために、10 億個の要素を含む配列を作成してはどうでしょうか? したがって、何らかの方法でこのハッシュ コードを 0 から 15 までの値に変換する必要があります (配列サイズが 16 の場合)。このために、追加の変換が使用されます。そこで、配列のサイズを最小限に抑えるためにインデックスを生成します。たとえば、Java 8 より前の HashMap では、目的のセルを見つけるために次の追加メソッドが使用されていました。

static int indexFor(int h, int length) {
        return h & (length-1);
}
入力として、作業の結果として得られたハッシュ コードhashCode()と内部配列の長さ (セルの数) を受け取りました。そして結果「ハッシュコード」→ビットごとの「AND」→(配列長 – 1)を返します。クラスはHashMapクラスを継承しAbstractMap、次のインターフェイスを実装します: MapCloneableSerializable。.method は Java のハッシュ関数を担当しますhashCode()。デフォルトの実装は、アイデンティティ ハッシュ コードhashCode()と呼ばれる値を返します。クラスが をオーバーライドした場合でも、 を呼び出すことでオブジェクトの ID ハッシュをいつでも取得できます。時々信じられているように、OpenJDK のデフォルト実装はメモリ アドレスとは何の関係もありません。詳細はこちら: habr HashMap では、ハッシュ テーブルは、単一リンクされたリストの配列 (より正確には、テーブルは拡張できるため動的) に基づいて実装されます。基本的に、メソッドの結果としてキーのハッシュ コードを取得し、それが変更され (後で説明します)、内部的には追加のメソッドを使用して、結果の値が必要なセルに分配されます。配列要素 (セル) はバケットとも呼ばれ、個々のノードを格納するために使用されます。各バケットはコレクション (リストまたはツリー) です。ノードは、ネストされたクラス(またはツリー構造)のオブジェクトです。実際、配列セル内には、別のクラス - の実装の基礎となる、単一リンクされたリスト、つまり赤黒ツリーのみが存在します。 hashCode()System.identityHashCode(Object o)hashCode()hashCode()NodeTreeNodeLinkedListTreeMap
HashMap クラスの詳細な分析 - 1
赤黒ツリーのオプションはそれほど頻繁には発生しません (どのように、何を、どこで - 以下)。この構造は理解するのが非常に難しいため、ノード タイプのノードに重点が置かれます。 ノードはHashMap内部に次のフィールドを持つ ネストされたクラスです。
HashMap クラスの詳細な分析 - 2
  • final int hash— 現在の要素のハッシュ。キーをハッシュした結果として取得されます。
  • final K key— 現在の要素のキー。ここには、メソッドの最初のオブジェクトとして指定した内容が書き込まれますput()
  • V value— 現在の要素の値。そして、メソッドの 2 番目のオブジェクトとして指定した内容がここに書き込まれますput()
  • Node < K, V> next— 同じバスケット内の次のノードへのリンク。リストは接続されているため、次のノードがある場合は、それへのリンクは必要ありません。
次に、HashMap クラス自体のフィールドを見てみましょう。
  • transient Node < K, V> [] table– キーと値のペアをノードの形式で保存するための、配列に基づいて実装されたハッシュ テーブル自体。ここにノードが保存されます。
  • transient int size— キーと値のペアの数。
  • int threshold— 要素の最大数。これに達すると、ハッシュ テーブルのサイズが 2 倍になります。式 (容量 * 負荷係数) を使用して計算されます。
  • final float loadFactor— このパラメータは、現在のハッシュ テーブルが新しいハッシュ テーブルを作成するために必要な負荷の程度を決定します。ハッシュ テーブルが 75% いっぱいになるとすぐに、新しいハッシュ テーブルが作成され、現在の要素がそこに移動されます (すべての要素を再ハッシュする必要があるため、コストのかかる操作です)。
  • transient Set< Map.Entry< K,V>> entrySet- キャッシュされた が含まれておりentrySet()、それを反復処理できますHashMap
そして定数:
  • static final int DEFAULT_INITIAL_CAPACITY= 1 << 4— デフォルトのハッシュ テーブル容量 (16);
  • static final int MAXIMUM_CAPACITY = 1 << 30— ハッシュ テーブルの可能な最大容量 (約 10 億)。
  • static final float DEFAULT_LOAD_FACTOR = 0.75f— デフォルトの負荷係数。
  • static final int TREEIFY_THRESHOLD = 81 つのバケット内の要素数の「しきい値」です。このしきい値に達すると、内部リンク リストがツリー構造 (赤黒ツリー) に変換されます。
  • static final int UNTREEIFY_THRESHOLD = 6— 1 つのバスケット内の要素の数が 6 に減少すると、ツリーからリンク リストへの逆遷移が発生します。
  • static final int MIN_TREEIFY_CAPACITY = 64— ツリー構造への遷移が可能なハッシュテーブルの最小容量(バケット数)。それらの。ハッシュ テーブルに少なくとも 64 個のバケットがあり、1 つのバケットに 8 つ以上の要素がある場合、ツリー構造への移行が発生します。
クラスコンストラクター:
  1. public HashMap()(capacity) = 16— デフォルトで、容量および負荷係数を使用してハッシュ表示を作成します(load factor) = 0.75
  2. public HashMap(Map< ? extends K, ? extends V> m)— 別のマッピングの要素を収容するのに十分な初期容量を持つ、別の指定されたマッピングの要素によって初期化されたハッシュ マッピングを作成します。
  3. public HashMap(int initialCapacity)— 指定された初期容量でハッシュ マップを作成します。HashMap が正しく機能するには、内部配列のサイズが 2 のべき乗 (つまり、16、64、128 など) である必要があります。
  4. public HashMap(int initialCapacity, float loadFactor)— 指定されたパラメータ(初期容量と負荷係数)を使用してハッシュ マップを作成します。
クラスはインターフェイスを実装し、独自のメソッドを追加せずにMapクラスを拡張します。ハッシュ マップは、その要素の順序を保証しません。したがって、要素がハッシュ マップに入力される順序は、必ずしも反復子によって取得される順序とは限りません。 オブジェクトの追加 キーと値のペアの追加は、 を使用して行われます。オブジェクトを追加するときに必要な手順を見てみましょう。 AbstractMapput()
  1. 入力されたオブジェクトのキーのハッシュ値が計算されます。キー ハッシュは、既知のキー メソッドstatic final int hash(Object key)にすでにアクセスしているメソッドによって計算されます。hashCode()これを行うには、オーバーライドされたメソッドhashCode()またはそのデフォルト実装が使用されます。メソッド内の追加の変換hash():

    
    static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    Почему бы просто не вычислить с помощью hashCode()? Это сделано из-за того, что hashCode() можно реализовать так, что только нижние биты int'a будут заполнены. Например, для Integer, Float – если мы в HashMap кладем маленькие значения, то у них и биты хеш-codeов будут заполнены только нижние. В таком случае ключи в HashMap будут иметь тенденцию скапливаться в нижних ячейках, а верхние будут оставаться пустыми, что не очень эффективно. На то, в Howой бакет попадёт новая запись, влияют только младшие биты хеша. Поэтому и придумали различными манипуляциями подмешивать старшие биты хеша в младшие, чтобы улучшить распределение по бакетам (чтобы старшие биты родного хеша an object начали вносить коррективы в то, в Howой бакет попадёт an object) и, How следствие, производительность. Потому и придумана дополнительная функция hash внутри HashMap.

  2. Вычисляем индекс бакета (ячейки массива), в который будет добавлен наш элемент:

    
    i = (n - 1) & hash

    где n – длина массива.

  3. Создается an object Node.

  4. Теперь, зная индекс в массиве, мы получаем список (цепочку) элементов, привязанных к этой ячейке. Если в бакете пусто, тогда просто размещаем в нем элемент. Иначе хэш и ключ нового element поочередно сравниваются с хешами и ключами элементов из списка и, при совпадении этих параметров, meaning element перезаписывается. Если совпадений не найдено, элемент добавляется в конец списка.

    Теперь к очень подробному примеру.

    1. Создадим an object класса HashMap:

      
      HashMap < String, Integer> map = new HashMap<>();
    2. С помощью метода put() добавим в хеш-отображение первую пару «ключ-meaning»:

      
      map.put("KING", 100);

      В этот момент внутри вызывается метод putVal().

    3. С помощью хеш-функции, роль которой играет метод hash, вычисляется хеш-code ключа, внутри которого предварительно вычисляется хеш-code с помощью метода hashCode() (в данном случае класса String), в результате чего мы получаем его «предварительное meaning» – 2306967. Может проверить в IDEA с помощью

      
      System.out.println("KING".hashCode());

      Полученный хеш-code модифицируется по формуле: (хеш-code) ^ (хеш-code>>> 16), и в результате получаем окончательный хеш-code – 2306996.

    4. Проверяем таблицу на «пустоту»:

      
      if ((tab = table) == null || (n = tab.length) == 0)

      где [] tab — сама хеш-table: ссылаем tab и table (напомню, что это поле класса HashMap, которое хранит массив для наших элементов) на один и тот же массив, а int n – дополнительная переменная-счетчик.

      Так How проверка вернёт true (потому что массив для таблицы не был создан с помощью оператора new в конструкторе), будет вызван метод resize(), который собственно и создаст таблицу размером на 16 элементов. Да-да, конструкторы класса ниHowой таблицы не создают. Вместо этого всегда происходит вызов метода resize() при первом добавлении element. Длина созданной таблицы (считай длина массива) будет записана в переменную n – n = (tab = resize()).length, которая в дальнейшем используется для вычисления бакета.

    5. 同時に、オブジェクトが配置されるバケットのインデックスを計算し、その中に要素がすでに存在するかどうかを確認します。計算します:

      
      i = (n - 1) & hash
      i = (16 - 1) & 2306996
      i = 4

      チェック:

      
      if ((p = tab[i = (n - 1) & hash]) == null)
    6. チェックの結果 true (バケット内に要素がない) が得られるため、次のフィールドを持つ Node オブジェクトが生成されます。

      
      {
      int hash = 2306996 — сгенерированный хеш-code
      String key = {"KING"} — ключ
      Integer value = 100 — meaning
      Node next = null — link на следующий узел
      }
      HashMap クラスの詳細な分析 - 3

      生成された Node オブジェクトは、インデックス [4] の下のバケットに追加されます。

      
      tab[i] = newNode(hash, key, value, null);
      tab[4] = newNode(2306996, “KING”, 100, null);

      newNode()Nodeクラスのオブジェクトを単純に返すメソッドです。

    7. 追加後、現在の要素数がパラメータを超えているかどうかがチェックされますthreshold

      
      if (++size > threshold)
          resize();

      resize()この値を超える場合は、ハッシュ テーブルのサイズを増やすメソッドが呼び出されます。

      この時点で、メソッドputVal()( と 、それぞれput()) は作業を完了します。

      結果は次のようにグラフで表すことができます。

      HashMap クラスの詳細な分析 - 4

      一般に、目的のバケットが空の場合にハッシュ テーブルにノード (キーと値のペア) を追加すると、次のようになります。次に、衝突を引き起こす要素を追加してみましょう (1 つのバケットに複数の要素がある場合)。

衝突について少し 異なるキーが (ハッシュが異なっていても) 同じバケット内に収まる状況は、衝突またはコリジョンと呼ばれます。ハッシュ テーブルがデータ セットより大きく、適切なハッシュ関数が選択されている場合でも、衝突が発生しないという保証はありません。そしてハッシュ値はint値の範囲(約40億)に限定されます。結果として得られる新しい値もどこかに書き込む必要があり、そのためには正確に書き込まれる場所を決定する必要があります。これは競合解決と呼ばれます。次の 2 つのアプローチがあります。
  • 外部チェーンまたはチェーン メソッド (HashMap で実装) - 例: セルには実際にはリスト (チェーン) が含まれています。また、リストにはすでに複数の値が含まれている場合があります (必ずしも同じハッシュ コードであるとは限りません)。
  • 線形プローブまたはオープン アドレッシング方法 (IdentityHashMap で実装) - ハッシュ関数が指すセルの後の最初の空のセルを検索することで構成されます。
衝突についてはここで読むことができます:クリックしてください
  1. このメソッドを使用して、put()別のキーと値のペアをハッシュ マッピングに追加します。

    
    map.put("BLAKE", 10);
  2. 「予備ハッシュ」 – 63281361 を計算します。それを変更し、その結果、最終ハッシュ コード – 63281940 を取得します。

  3. 「空」の最初のチェックでは false が返されるため (テーブルを作成する必要はありません)、オブジェクトが配置されるバケットのインデックスをすぐに計算します。

    
    i = (n - 1) & hash
    i = (16 - 1) & 63281940
    i = 4
  4. 指定されたインデックスのバケット内に要素が存在するかどうかがチェックされます。この場合は条件がif ((p = tab[i = (n - 1) & hash]) == null)満たされないため (バケット内にすでに要素が存在します)、ブロックに進みますelse

  5. まず、追加される要素とバケット内のリンク リストの最初の要素を比較します。

    
    (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))

    При проверке сначала сравниваются хеши ключей. Если этот «участок» (p.hash == hash) возвращает false, тогда остальная часть условия игнорируется (&&), так How an objectы гарантированно разные. Иначе затем сравниваются ключи по ссылке (==) и в случае неequalsства, ключи сравниваются уже посредством метода equals(). Сравнение осуществляется в таком порядке во благо производительности. Если все три выражения возвращают true, это означает, что ключи равны и новый элемент будет записан в дополнительную переменную, чтобы в дальнейшем с её помощью перезаписать meaning у ключа:

    
    if (e != null) { // existing mapping for key
          V oldValue = e.value;
          if (!onlyIfAbsent || oldValue == null)
          e.value = value;
          afterNodeAccess(e);
           return oldValue;
     }

    В результате сравнения ключей мы получаем false уже на первом этапе (разные хеши).

  6. Игнорируем condition (p instanceof TreeNode), так How структура в бакете не является древовидной на данном этапе.

  7. Далее переходим в цикл for, где в пределах одного бакета проверяем у элементов указатель на следующий элемент next, и если он equals null (значит элемент в списке последний и единственный), добавляем новый элемент Node в конец списка:

    
    if ((e = p.next) == null){
    	p.next = newNode(hash, key, value, null)
    ... };

    Вы можете спросить, а где же проверка на equalsство ключей? Мы же не можем просто взять и добавить новый элемент. Так вот она уже была заранее осуществлена в пункте (5). Благодаря этому, теперь мы можем просто проверить указатель этого element, и так How он сейчас equals null, можно сделать вывод о том, что в списке только один элемент. И так How мы уже проверяли их ключи, мы можем безболезненно добавлять новый элемент.

    Если же при первой итерации указатель не equals null, это говорит о том, что в списке How минимум два element, поэтому в таком случае мы переходим к следующему условия и сравниваем ключ добавляемого element со всеми ключами элементов в списке (способом, описанным в пятом пункте).

    
    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))

    Если при сравнении ключей будет найдено совпадение, новый элемент будет записан в дополнительную переменную, чтобы в дальнейшем с её помощью перезаписать meaning у ключа.

    После добавления второго element наш an object HashMap графически выглядит так:

    HashMap クラスの詳細な分析 - 5

    В итоге, добавление элементов при коллизии (в пределах одного бакета) выглядит следующим образом:

    • проверяем с помощью методов hashCode() и equals(), что оба ключа одинаковы.
    • если ключи одинаковы, заменить текущее meaning новым.
    • иначе связать новый и старый an objectы с помощью структуры данных "связный список", указав ссылку на следующий an object в текущем и сохранить оба под нужным индексом; либо осуществить переход к древовидной структуре
  8. После каждой итерации (добавления нового element) в цикле for увеличивается счетчик, который отвечает за количество элементов в бакете:

    
    for (int binCount = 0; ; ++binCount)

    До тех пор, пока их количество не станет равно or больше 7:

    
    binCount >= TREEIFY_THRESHOLD – 1

    В таком случае произойдет вызов метода treeifyBin()treeify()для непосредственного построения древовидной структуры. Однако, если количество бакетов в текущей хеш-таблице меньше 64:

    
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)

    ツリー構造に進む代わりに、メソッドが呼び出されてresize()ハッシュ テーブルのサイズが増加し、要素が再分散されます。treeify()次に、リンクされたリストがTreeNode赤黒ツリーに変換されます。このメソッドは、resize()現在のストレージのすべての要素を反復処理し、(新しいサイズを考慮して) インデックスを再計算し、要素を新しい配列に再分配します。

    赤黒ツリーの構造の詳細には触れずに簡単に説明すると、次のことが起こります。

    1. リストの最初の要素は、ツリー全体のルート (黒) として書き込まれます。

      
      if (root == null) {
          x.parent = null;
          x.red = false;
          root = x;
      }
    2. 他の要素の場合:

      ハッシュ値に応じて、それらを左または右に分配します。

      
      if ((ph = p.hash) > h)
          dir = -1;
      else if (ph < h)
          dir = 1;

      左側のすべての子はルート ノード以下 (または同等) である必要があり、右側のすべての子はそれより大きくなければなりません。

    3. 2 つの要素が同じハッシュを持ち、他の方法で比較できない場合 (インターフェイスが実装されていない場合Comparable)、ツリーの構築を中断し、メソッド を呼び出しますtieBreakOrder()。次に、ネイティブ メソッドを使用してSystem.identityHashCode()グローバルに一意のハッシュ コードを計算します。 。

      詳細はこちら:記事へのリンク

    4. TreeNode子 (左または右) ゼロ要素が見つかるまで、ツリー要素(オブジェクト) をチェックします。

      
      if ((p = (dir <= 0) ? p.left : p.right) == null)
    5. 子ノードを追加します (ディレクトリに応じて左または右)。

      
      x.parent = xp;
      if (dir <= 0)
          xp.left = x;
      else
          xp.right = x;
    6. 新しい要素を追加するとツリーのバランスが崩れる可能性があるため、再バランスのためのメソッドを呼び出します。

      
      root = balanceInsertion(root, x);

      CCH のバランスについては、ここで読むことができます: habr

    7. バランス調整後、ルート要素が変更される場合があります。これを修正するには、渡されるルートが最初のノードになることを保証するメソッドを呼び出します。

      
      moveRootToFront(tab, root)

      ここでは、赤黒ツリーがどのように構築され、自己バランスが保たれているかを明確に見ることができます:視覚化

原則としてこれですべてです。例を使用して、KING、MARY、JOSE、ANNA、FRED、TONY、ALEX、PEPE の名前をキーとして追加するとします。そして、ハッシュ テーブルに少なくとも 64 個のバケットがあり、これらすべての要素が 1 つのバケットに蓄積されているとします。構造的には、このバケットは次のようになります (要素はハッシュ コードによって並べ替えられます): CCHD のタイプ:
HashMap クラスの詳細な分析 - 6
バケットの内部を表示します。
HashMap クラスの詳細な分析 - 7
要素の取得(キーによる値の取得) 追加操作に関しては非常に簡単です。アルゴリズム (バケット内にリンクされたリストがある場合) は次のように記述できます。
  1. キーのハッシュ コードを計算します。
  2. バケットインデックスを計算します。
  3. インデックスを調べて、最初の要素のキーを既存の値と比較します。等しい場合は値を返し、そうでない場合は次の要素が存在するかどうかを確認します。
  4. 次の Node オブジェクトが null の場合は、null を返します。
  5. 次の Node オブジェクトが null でない場合は、そのオブジェクトに移動し、要素が見つかるか次の Node オブジェクトが null になるまで最初の 3 つの手順を繰り返します。
このメソッドを使用して、get()「KING」キーの値を取得します。

map.get("KING");
内部ではメソッドが呼び出されgetNode(int hash, Object key)、キー自体 (「KING」) とそのハッシュ (2306996) が渡されます。これは操作中と同じ方法で事前に計算されますput()
  1. 私たちは以下をチェックします:

    1. ハッシュテーブルは存在しますか?(tab = table) != null

      HashMap を作成するとき、テーブルの配列はコンストラクターで作成されないことに注意してください。これは、resize()ハッシュ テーブルに最初の要素を追加するときに常に呼び出されるメソッドの後半で行われます。したがって、HashMap に要素が追加されていない場合は、要素を格納する内部配列が存在しないだけです。

    2. 前の式が true を返した場合は、内部配列の長さが 0 より大きいことを確認する必要があります。(n = tab.length) > 0;

    3. 前の式も true を返す場合は、以前に計算されたインデックス (この場合は 4) のバケットに移動し、要素の存在を確認します。

      
      (first = tab[(n - 1) & hash]) != null)
    4. 探しているキーをバケット内のリストの最初の要素のキーと比較します。これは、ほとんどのバケットでは (衝突が発生するすべての場所ではありません) 要素が 1 つしかないためです (今回の場合)。メソッドの場合と同様にput()、ハッシュが比較され、一致する場合、キーは参照によって比較され、その後にのみ比較されますequals()

      
      if (first.hash == hash && // always check first node
          ((k = first.key) == key || (key != null && key.equals(k))))

      この例では、キー「KING」がキー「BLAKE」の前にあるため(リンクされたリスト内では、新しい要素は最後に追加され、KING が最初に追加されます)、この時点で停止し、次の最初のオブジェクトを返します。 get() メソッドに Node を入力すると、値 (100) を持つフィールドが彼から「奪われます」。

      
      return (e = getNode(hash(key), key)) == null ? null : e.value;
  2. バケット内に複数の要素がある場合は、次のようになります。

    1. do – whileバケットがリンク リストの場合は、一致するものが見つかるまでループ内の各要素を介してリストを調べます。

      
      do {
          if (e.hash == hash &&
              ((k = e.key) == key || (key != null && key.equals(k))))
              return e;
      } while ((e = e.next) != null);
    2. バケットがツリー構造の場合、メソッドがさらに呼び出されgetTreeNode()、メソッドが使用されて必要なキーが検索されますfind()。ツリー検索を実行します。ハッシュが比較され、検索する左右のルート ノードが決定されます。キーが等しい場合 (参照または によってequals)、このノードを返します。左または右の子ノードが null の場合は、さらに CompareTo を使用してキーを比較します (キーがインターフェースを実装している場合Comparable)。それ以外の場合は、一致するものが見つかるまでツリー (右または左のサブツリー) を介して再帰的検索を実行します。

HashMap からのオブジェクトの削除 記事のスペースが少なくなってきたので、キーごとに削除がどのように行われるかを簡単に説明します。アルゴリズムは非常に似ています。
  • 目的のバケットに移動します (これも事前に計算されています)。

  • バケット内にオブジェクトが 1 つだけある場合 (ヌル ポインタを確認します)、ハッシュ、リンク、およびequals(突然ハッシュが等しくない場合) を比較します。一致するものが見つかりましたか? これは私たちのキーです。削除して (=null)、このキーの値を返します。

  • バケット内に複数の要素がある場合は、要素が見つかるかリストの最後に到達するまで、ループで各要素をチェックします。

  • 要素が見つからなかった場合は、null を返します。

ツリーの状況では、かなりトリッキーな実装がありますが、これについては知らないで安眠する方が良いです (メソッドの説明には、実装が赤黒の通常の削除操作よりも複雑であるとさえ書かれています)木)。さらに、削除すると、1 つのバケット内のノードの数が 6 に戻り、ツリーがリンク リストに再構築されます。長年の経験を持つ開発者でない場合、これを知って理解する必要はまったくありません (そして単に必要ありません)。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION