Java HashMapは同じハッシュコードで異なるオブジェクトをどのように処理しますか?


223

私の理解に従って、私は思います:

  1. 2つのオブジェクトが同じハッシュコードを持つことは完全に合法です。
  2. 2つのオブジェクトが(equals()メソッドを使用して)等しい場合、それらは同じハッシュコードを持っています。
  3. 2つのオブジェクトが等しくない場合、同じハッシュコードを持つことはできません

私は正しいですか?

ここで正しければ、次の質問HashMapがあります。内部でオブジェクトのハッシュコードを使用しています。では、2つのオブジェクトが同じハッシュコードを持つことができる場合、どのHashMapキーを使用してどのオブジェクトを追跡できるのでしょうか。

HashMap内部でオブジェクトのハッシュコードをどのように使用するかを誰かが説明できますか?


29
レコードの場合:#1と#2は正しいですが、#3は間違っています:等しくない2つのオブジェクトが同じハッシュコードを持つ可能性があります。
Joachim Sauer

6
#1と#3は矛盾しています
Delfic

実際、#2に従わない場合、equals()実装(またはおそらくhashCode())は正しくありません。
Joachim Sauer

回答:


346

ハッシュマップは次のように機能します(これは少し簡略化されていますが、基本的なメカニズムを示しています)。

キーと値のペアを格納するために使用する「バケット」の数があります。各バケットには一意の番号があり、それがバケットを識別します。キーと値のペアをマップに入れると、ハッシュマップはキーのハッシュコードを調べ、そのペアを、識別子がキーのハッシュコードであるバケットに格納します。例:キーのハッシュコードは235です->ペアはバケット番号235に保存されます(1つのバケットが複数のキーと値のペアを保存できることに注意してください)。

ハッシュマップでキーを指定して値を検索すると、最初に、指定したキーのハッシュコードが確認されます。ハッシュマップは対応するバケットを調べ、指定したキーとバケット内のすべてのペアのキーを比較しますequals()

これで、これがマップ内のキーと値のペアを検索するのに非常に効率的であることがわかります。キーのハッシュコードによって、ハッシュマップはどのバケットを参照するかをすぐに認識できるため、そのバケットの内容に対してテストするだけで済みます。

上記のメカニズムを見ると、キーのメソッドhashCode()equals()メソッドに必要な要件もわかります。

  • 2つのキーが同じ(比較するとequals()返さtrueれる)場合、それらのhashCode()メソッドは同じ数を返す必要があります。キーがこれに違反する場合、等しいキーは異なるバケットに格納される可能性があり、ハッシュマップはキーと値のペアを見つけることができません(同じバケットを検索するため)。

  • 2つのキーが異なる場合、それらのハッシュコードが同じかどうかは関係ありません。ハッシュコードが同じ場合、それらは同じバケットに格納されます。この場合、ハッシュマップはequals()それらを区別するために使用します。


4
「そして、ハッシュマップはキーと値のペアを見つけることができません(同じバケットを調べるため)。」同じバケットを調べようとしていることを説明できますか?これら2つの等しいオブジェクトはt1とt2であり、それらは等しく、t1とt2にはそれぞれハッシュコードh1とh2があるので、t1.equals(t2)= trueとh1!= h2したがって、ハッシュマップがt1を探す場合、バケットh1とt2をバケットt2で探しますか?
オタク2012

19
2つのキーは等しいが、それらのhashCode()メソッドが異なるハッシュコードを返す場合、キークラスのequals()およびhashCode()メソッドが規約に違反しているため、でこれらのキーを使用すると、奇妙な結果が得られますHashMap
Jesper、

各バケットは、内部的にリンクリストを使用する複数のキー値のペアを持つことができます。しかし、私の混乱は-バケツとは何ですか?内部で使用するデータ構造は何ですか?バケット間に接続はありますか?
Ankit Sharma 14

1
@AnkitSharmaすべての詳細を本当に知りたい場合は、JDKインストールディレクトリのHashMapファイルsrc.zipにあるのソースコードを検索してください。
Jesper

1
@ 1290同じバケット内のキー間の唯一の関係は、それらが同じハッシュコードを持つことです。
Jesper

88

3番目のアサーションは正しくありません。

2つの等しくないオブジェクトが同じハッシュコードを持つことは完全に合法です。これHashMapは「ファーストパスフィルター」として使用され、マップが指定されたキーを持つ可能なエントリをすばやく見つけることができます。次に、同じハッシュコードのキーが指定されたキーと等しいかどうかがテストされます。

2つの等しくないオブジェクトが同じハッシュコードを持つことができないという要件は望ましくありません。そうでなければ、2 32の可能なオブジェクトに制限されます。(また、他のクラスが同じハッシュを生成する可能性があるため、異なるタイプがオブジェクトのフィールドを使用してハッシュコードを生成することさえできなかったことも意味します。)


6
どのようにして2 ^ 32の可能なオブジェクトに到達しましたか?
オタク

5
私は遅れていますが、まだ疑問に思っている人のために:Javaのハッシュコードはintであり、intには2 ^ 32の可能な値があります
Xerus

69

HashMap構造図

HashMapEntryオブジェクトの配列です。

HashMapオブジェクトの単なる配列と考えてください。

これObjectが何であるかを見てください:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
 
}

Entryオブジェクトはキーと値のペアを表します。バケットに複数のがある場合、フィールドnextは別のEntryオブジェクトを参照しますEntry

2つの異なるオブジェクトのハッシュコードが同じである場合があります。この場合、2つのオブジェクトが1つのバケットに保存され、リンクされたリストとして表示されます。エントリポイントは、最近追加されたオブジェクトです。このオブジェクトは、nextフィールドなどを持つ別のオブジェクトを参照します。最後のエントリはを参照していnullます。

HashMapデフォルトのコンストラクタでを作成すると

HashMap hashMap = new HashMap();

アレイは、サイズ16でデフォルトの0.75ロードバランスで作成されます。

新しいキーと値のペアを追加する

  1. キーのハッシュコードを計算する
  2. hash % (arrayLength-1)要素を配置する位置(バケット番号)を計算します
  3. にすでに保存されているキーを使用して値を追加しようとすると、HashMap値が上書きされます。
  4. そうでない場合、要素がバケットに追加されます。

バケットにすでに少なくとも1つの要素がある場合、新しい要素が追加され、バケットの最初の位置に配置されます。そのnextフィールドは古い要素を参照します。

削除

  1. 指定されたキーのハッシュコードを計算します
  2. バケット数を計算する hash % (arrayLength-1)
  3. バケット内の最初のエントリオブジェクトへの参照を取得し、equalsメソッドを使用して、指定されたバケット内のすべてのエントリを反復処理します。最終的には正しいものを見つけますEntry。目的の要素が見つからない場合は、null

3
これは間違っているhash % (arrayLength-1)、それは次のようになりますhash % arrayLength。しかし、それは実際です hash & (arrayLength-1)。つまり2^n、配列の長さに2の累乗()を使用するため、n最下位ビットが使用されます。
ウェストン2017年

Entityオブジェクトの配列ではなく、LinkedList / Treeの配列だと思います。また、各ツリーには内部的にEntityオブジェクトがあります。
Mudit bhaintwal 2017年

@shevchykなぜキーとハッシュを保存するのですか?それらの用途は何ですか?ここで記憶を浪費していませんか?
roottraveller 2017

hashsetは内部的にハッシュマップを使用します。ハッシュマップの追加および削除ルールはハッシュセットに適していますか?
エクスチェンジ、2017年

2
@Westonだけでなく、hashCodeはintもちろん負の数になる可能性があります。負の数でモジュロを実行すると負の数になります
Eugene

35

http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.htmlで優れた情報を見つけることができます

要約する:

HashMapはハッシュの原理で機能します

put(key、value): HashMapは、キーオブジェクトと値オブジェクトの両方をMap.Entryとして格納します。Hashmapはhashcode(key)を適用してバケットを取得します。衝突がある場合、HashMapはLinkedListを使用してオブジェクトを格納します。

get(key): HashMapはKey Objectのハッシュコードを使用してバケットの場所を見つけ、keys.equals()メソッドを呼び出してLinkedList内の正しいノードを識別し、Java HashMapでそのキーに関連付けられた値オブジェクトを返します。


3
Jasperから提供された回答の方が良いと感じました。ブログは、概念を理解するよりも、インタビューを処理することに向いていると感じました
Narendra N

@NarendraN私はあなたに同意します。
Abhijit Gaikwad 2014

22

バージョンHashMapのメカニズムの大まかな説明は次のとおりです(Java 6とは少し異なる場合があります)Java 8


データ構造

  • ハッシュテーブル
    ハッシュ値はhash()キーで計算され、特定のキーに使用するハッシュテーブルのバケットを決定します。
  • リンクリスト (単独)
    バケット内の要素の数が少ない場合、単一リンクリストが使用されます。

  • 黒木バケット内の要素の数が多い場合、赤黒木が使用されます。

クラス(内部)

  • Map.Entry
    マップ内の単一のエンティティ、キー/値エンティティを表します。
  • HashMap.Node
    ノードのリンクリストバージョン。

    以下を表すことができます。

    • ハッシュバケット。
      ハッシュプロパティがあるからです。
    • 単一リンクリスト内のノード(したがって、linkedlistの先頭)
  • HashMap.TreeNode
    ノードのツリーバージョン。

フィールド(内部)

  • Node[] table
    バケットテーブル(リンクリストの先頭)。
    バケットに要素が含まれていない場合、それはnullであるため、参照のスペースのみを使用します。
  • Set<Map.Entry> entrySet エンティティのセット。
  • int size
    エンティティの数。
  • float loadFactor
    サイズを変更する前に、ハッシュテーブルをどの程度使用できるかを示します。
  • int threshold
    サイズを変更する次のサイズ。
    式:threshold = capacity * loadFactor

メソッド(内部)

  • int hash(key)
    キーでハッシュを計算します。
  • ハッシュをバケットにマッピングする方法は?
    次のロジックを使用します。

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }

容量について

ハッシュテーブルでは、容量はバケット数を意味しますtable.length。および
を介して計算することもできるため、クラスフィールドとして定義する必要はありません。thresholdloadFactor

次の方法で有効容量を取得できます: capacity()


操作

  • キーでエンティティを検索します。
    最初にハッシュ値でバケットを見つけ、次にリンクされたリストをループするか、ソートされたツリーを検索します。
  • キーを持つエンティティを追加します。
    まず、キーのハッシュ値に従ってバケットを見つけます。
    次に、値を見つけてみます。
    • 見つかった場合は、値を置き換えます。
    • それ以外の場合は、リンクされたリストの先頭に新しいノードを追加するか、ソートされたツリーに挿入します。
  • Resize
    threshold達すると、ハッシュテーブルの容量が2倍になり、table.lengthすべての要素に対して再ハッシュが実行され、テーブルが再構築されます。
    これは高価な操作になる可能性があります。

パフォーマンス

  • get&put
    時間の複雑さはO(1)です。
    • バケットは、従って、配列インデックスを介してアクセスされますO(1)
    • 各バケットのリンクリストは長さが短いため、次のように表示できます O(1)
    • ツリーのサイズも制限されてO(1)O(log N)ます。これは、要素数が増加すると容量が拡張され、ハッシュが再ハッシュされるため、ではなくと表示される可能性があるためです。

例を挙げてください。時間の複雑さはO(1)
Jitendra 2017

@jsroyalこれは複雑さをより明確に説明するかもしれません:en.wikipedia.org/wiki/Hash_table。しかし、簡単に言うと、ターゲットバケットの検索はO(1)です。次に、バケット内では、要素の量は少なく、ハッシュテーブル全体の要素の総数に関係なく、平均して定数です。したがって、バケット内のターゲット要素の検索もO(1)です。したがって、O(1)+ O(1)= O(1)です。
Eric Wang

14

ハッシュコードは、ハッシュマップがチェックするバケットを決定します。バケット内に複数のオブジェクトがある場合は、線形検索が実行され、バケット内のどのアイテムが(equals())メソッドを使用して目的のアイテムと等しいかがわかります。

つまり、完全なハッシュコードがある場合、ハッシュマップアクセスは一定であり、バケットを反復処理する必要はありません(技術的にはMAX_INTバケットも必要です。Java実装は、同じバケット内のいくつかのハッシュコードを共有して、スペース要件を削減します)。最悪のハッシュコード(常に同じ番号を返す)がある場合、必要なものを取得するためにマップ内のすべてのアイテム(すべて同じバケット内にある)を検索する必要があるため、ハッシュマップアクセスは線形になります。

ほとんどの場合、適切に記述されたハッシュコードは完全ではありませんが、多かれ少なかれ一定のアクセスを提供するのに十分ユニークです。


11

あなたはポイント3で間違っています。2つのエントリは同じハッシュコードを持つことができますが、等しくない場合があります。OpenJdkからのHashMap.getの実装を見てください。ハッシュが等しいこととキーが等しいことを確認することがわかります。ポイント3が真であれば、キーが等しいことを確認する必要はありません。前者はより効率的な比較であるため、ハッシュコードはキーの前に比較されます。

これについてもう少し詳しく知りたい場合は、OpenJdk実装が使用しているメカニズムであると私が考えている、Open Addressingの衝突解決に関するWikipediaの記事をご覧ください。そのメカニズムは、他の回答の1つが言及している「バケット」アプローチとは微妙に異なります。


6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

したがって、ここでは、オブジェクトS1とS2の両方のコンテンツが異なる場合、オーバーライドされたHashcodeメソッドが両方のオブジェクトに対して異なるHashcode(116232,11601)を生成することを確認しています。今は異なるハッシュコードがあるので、EQUALSメソッドを呼び出すことさえわずらわしくありません。オブジェクト内の異なるハッシュコードが異なるコンテンツを保証するからです。

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 

3

2つのオブジェクトが等しい場合、それらは同じハッシュコードを持っているが、その逆はないことを意味します。

2つの等しいオブジェクト------>それらは同じハッシュコードを持っています

2つのオブジェクトが同じハッシュコードを持っている---- xxxxx->それらは等しくない

HashMapのJava 8アップデート-

あなたはあなたのコードでこの操作を行います-

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

そう、あなたのハッシュコードは、両方のキーに対して返さ仮定"old""very-old"同じです。それから何が起こるでしょう。

myHashMapはHashMapであり、最初に容量を指定しなかったとします。つまり、Javaによるデフォルトの容量は16です。そのため、新しいキーワードを使用してハッシュマップを初期化するとすぐに、16のバケットが作成されました。今あなたが最初のステートメントを実行したとき

myHashmap.put("old","old-value");

次に、ハッシュコード"old"が計算されます。ハッシュコードも非常に大きな整数になる可能性があるため、Javaが内部でこれを実行しました(ここではハッシュがハッシュコードであり、>>>は右シフトです)。

hash XOR hash >>> 16

その大きな画像として与えるために、それが今、あなたのキーと値のペア0〜15の間であろう、いくつかのインデックスを返します"old"し、"old-value"Entryオブジェクトのキーと値のインスタンス変数に変換されます。次に、このエントリオブジェクトはバケットに格納されます。または、特定のインデックスでこのエントリオブジェクトが格納されると言うことができます。

FYI- Entryは、Mapインターフェース-Map.Entryのクラスで、これらの署名/定義が含まれています

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

ここで、次のステートメントを実行すると-

myHashmap.put("very-old","very-old-value");

"very-old"同じハッシュコードを与える"old"ため、この新しいキーと値のペアは同じインデックスまたは同じバケットに再び送信されます。ただし、このバケットは空ではないnextため、Entryオブジェクトの変数を使用して、この新しいキーと値のペアを格納します。

これは、同じハッシュコードを持つすべてのオブジェクトのリンクリストとして保存されますが、TRIEFY_THRESHOLDは値6で指定されます。そのため、これに達した後、リンクリストは最初の要素がルート。


すばらしい答え(y)
Sudhanshu Gaur 2017

2

各項目オブジェクトは、キーと値のペアを表します。バケットに複数のエントリがある場合、フィールドは次に他のエントリオブジェクトを参照します。

2つの異なるオブジェクトのhashCodeが同じである場合があります。この場合、2つのオブジェクトが1つのバケットに保存され、LinkedListとして表示されます。エントリポイントは、最近追加されたオブジェクトです。このオブジェクトは、次のフィールドを持つ他のオブジェクトも参照します。最後のエントリはnullを参照しています。デフォルトのコンストラクターでHashMapを作成する場合

配列はサイズ16とデフォルトの0.75ロードバランスで作成されます。

ここに画像の説明を入力してください

(ソース)


1

ハッシュマップはハッシュの原理で機能します

HashMap get(Key k)メソッドは、キーオブジェクトのhashCodeメソッドを呼び出し、返されたhashValueを独自の静的ハッシュ関数に適用して、キーと値がEntry(Map。エントリー)。したがって、前の行から、キーと値の両方がエントリオブジェクトの形式としてバケットに格納されると結論しました。したがって、値のみがバケットに格納されると考えるのは正しくなく、インタビュアーに良い印象を与えません。

  • HashMapオブジェクトでget(Key k)メソッドを呼び出すときはいつでも。まず、キーがnullかどうかをチェックします。HashMapに存在できるnullキーは1つだけであることに注意してください。

keyがnullの場合、nullキーは常にハッシュ0にマップされるため、インデックス0になります。

keyがnullでない場合、keyオブジェクトのハッシュ関数を呼び出します。上記のメソッドの4行目、つまりkey.hashCode()を参照してください。したがって、key.hashCode()がhashValueを返した後、4行目は次のようになります。

            int hash = hash(hashValue)

そして今、それは返されたhashValueをそれ自身のハッシュ関数に適用します。

なぜhash(hashValue)を使用して再度ハッシュ値を計算しているのか不思議に思うかもしれません。答えは質の悪いハッシュ関数から守ります。

これで、最終的なハッシュ値を使用して、エントリオブジェクトが格納されているバケットの場所が検索されます。エントリオブジェクトは次のようにバケットに格納されます(ハッシュ、キー、値、バケットインデックス)


1

HashMapがどのように機能するかについては詳しく説明しませんが、実際に関連付けることでHashMapがどのように機能するかを思い出せるように、例を示します。

Key、Value、HashCode、バケットがあります。

しばらくの間、それらのそれぞれを以下と関連付けます。

  • バケット->社会
  • HashCode->社会のアドレス(常に一意)
  • 価値->社会の家
  • キー->家の住所。

Map.get(key)の使用:

スティービーは、VIP社会の別荘に住んでいる友人の(ジョス)家に行きたいと思っています。それをJavaLovers社会にしましょう。Josseのアドレスは彼のSSNです(これはすべての人にとって異なります)。SSNに基づいて協会の名前を見つけるために維持されているインデックスがあります。このインデックスは、HashCodeを見つけるためのアルゴリズムと考えることができます。

  • SSN協会の名前
  • 92313(ジョス)-JavaLovers
  • 13214-AngularJSLovers
  • 98080-JavaLovers
  • 53808-BiologyLovers

  1. このSSN(key)は、最初に、社会の名前に他ならないHashCode(インデックステーブルから)を提供します。
  2. 現在、複数の家が同じ社会に属している可能性があるため、HashCodeは一般的です。
  3. 社会が2つの家に共通していると仮定します。はい、家の住所にすぎない(SSN)キーを使用して、どの家に行くのかをどのように特定しますか。

Map.put(key、Value)の使用

これは、HashCodeを見つけることによってこのValueに適した社会を見つけ、次に値が格納されます。

これがお役に立てば幸いです。


0

それは長い答えになるだろう、飲み物をつかんで読んで…

ハッシュとは、より速く読み書きできるメモリにキーと値のペアを格納することです。キーは配列に、値はLinkedListに格納します。

4つのキーと値のペアを保存したいとしましょう-

{
girl => ahhan , 
misused => Manmohan Singh , 
horsemints => guess what”, 
no => way
}

したがって、キーを格納するには、4つの要素の配列が必要です。これらの4つのキーの1つを4つの配列インデックス(0、1、2、3)にマップするにはどうすればよいですか?

したがって、javaは個々のキーのhashCodeを見つけ、それらを特定の配列インデックスにマップします。ハッシュコードの数式は-

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

ハッシュとガール!! 私はあなたが何を考えているか知っています。そのワイルドなデュエットに対するあなたの魅力は、あなたが重要なことを見逃してしまうかもしれません。

なぜjavaは31を掛けますか?

31は2 ^ 5 – 1の形式の奇数の素数だからです。そして奇数の素数はハッシュ衝突の可能性を減らします

さて、このハッシュコードはどのように配列インデックスにマッピングされていますか?

答えは、Hash Code % (Array length -1) です。だから、“girl”にマッピングされている(3173020 % 3) = 1我々の場合には。これは配列の2番目の要素です。

値「ahhan」は、配列インデックス1に関連付けられたLinkedListに格納されます。

HashCollision-hasHCodeキーを見つけよう“misused”“horsemints”し、上記の式を 使用しようとすると、どちらも同じように表示され1069518484ます。わぁぁぁぁぁぁぁぁ!学んだ教訓-

2つの等しいオブジェクトは同じhashCodeを持つ必要がありますが、hashCodeが一致する場合、オブジェクトが等しい場合の保証はありません。したがって、「誤用」と「horsemints」に対応する両方の値をバケット1(1069518484%3)に格納する必要があります。

ハッシュマップは次のようになります–

Array Index 0 
Array Index 1 - LinkedIst (“ahhan , Manmohan Singh , guess what”)
Array Index 2  LinkedList (“way”)
Array Index 3  

本体がキーの値を見つけようとすると“horsemints”、javaはすぐにそのハッシュコードを見つけてモジュール化し、対応するLinkedListでその値の検索を開始しindex 1ます。したがって、この方法では、4つの配列インデックスをすべて検索する必要がないため、データアクセスが高速になります。

しかし、ちょっと待ってください。そのlinkedListに対応する配列インデックス1には3つの値があり、どの値がキー「horsemints」の値であったかをどのようにして見つけますか?

実際、私は嘘をつきました。私が言ったとき、HashMapは値をLinkedListに格納するだけです。

両方のキーと値のペアをマップエントリとして保存します。実際のマップは次のようになります。

Array Index 0 
Array Index 1 - LinkedIst (<”girl => ahhan”> , <” misused => Manmohan Singh”> , <”horsemints => guess what”>)
Array Index 2  LinkedList (<”no => way”>)
Array Index 3  

これで、ArrayIndex1に対応するlinkedListをトラバースするときに、実際に各エントリのキーとそのLinkedListのキーを「horsemints」と比較し、1つが見つかると、その値を返すだけです。

あなたがそれを読んでいる間あなたが楽しんでいたらいいのに:)


これは間違っていると思います。「キーは配列に格納され、値はLinkedListに格納されます。」
ACV

各バケットのリスト内の各要素には、キーと値、および次のノードへの参照が含まれています。
ACV

0

言われているように、絵は1000語の価値があります。私は言う:いくつかのコードは1000語よりも優れています。これがHashMapのソースコードです。Getメソッド:

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

したがって、「バケット」を見つけるためにハッシュが使用され、最初の要素が常にそのバケットでチェックされることが明らかになります。そうでない場合は、equalsキーを使用して、リンクされたリストで実際の要素を検索します。

put()メソッドを見てみましょう:

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

少し複雑ですが、新しい要素がハッシュに基づいて計算された位置のタブに配置されていることが明らかになります。

i = (n - 1) & hashこれiは、新しい要素が配置されるインデックスです(または「バケット」です)。配列nのサイズですtab(「バケット」の配列)。

まず、その「バケット」の最初の要素として配置されるようにします。要素がすでにある場合は、リストに新しいノードを追加します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.