web-dev-qa-db-ja.com

Java.util.HashMapおよびHashSetの内部実装

_Java.util.HashMap_と_Java.util.HashSet_の内部実装を理解しようとしています。

しばらくの間、次のような疑問が浮かんできました。

  1. HashMap/HashSetの@Override public int hashcode()の重要性は何ですか?このハッシュコードはどこで内部的に使用されていますか?
  2. 私は通常、HashMapのキーが_myMap<String,Object>_のようなStringであることを見てきました。 _myMap<someObject, Object>_のように(文字列ではなく)someObjectに対して値をマッピングできますか?これが成功するために私が従う必要のあるすべての契約は何ですか?

前もって感謝します !

編集:

  1. キーのハッシュコード(チェック!)は、ハッシュテーブルで値がマップされる実際のものであると言っていますか?また、myMap.get(someKey); Javaは内部的にsomeKey.hashCode()を呼び出して、ハッシュテーブルの数値を取得し、結果の値を探します。

Answer:はい。

編集2:

  1. _Java.util.HashSet_では、ハッシュテーブルのキーはどこから生成されますか?追加するのはオブジェクトからですか。 mySet.add(myObject);次にmyObject.hashCode()は、これがハッシュテーブルのどこに配置されるかを決定しますか? (HashSetでキーを指定しないため)。

Answer:追加されたオブジェクトがキーになります。値はダミーです!

18
peakit

質問2の答えは簡単です。はい、好きなオブジェクトを使用できます。文字列型のキーを持つマップは、ネーミングサービスの一般的なデータ構造であるため、広く使用されています。ただし、一般的には、Map<Car,Vendor>Map<Student,Course>のような2つのタイプをマップできます。

Hashcode()メソッドについては、以前に答えられたようなものです。equals()をオーバーライドする場合は常に、契約に従うためにhashcode()をオーバーライドする必要があります。一方、equals()の標準実装に満足している場合は、hashcode()に触れないでください(コントラクトを破り、等しくないオブジェクトのハッシュコードが同一になる可能性があるため)。

実用的な補足:Eclipse(そしておそらく他のIDEも)は、クラスメンバーに基づいて、クラスのequals()とhashcode()の実装のペアを自動生成できます。

編集

あなたの追加の質問のために:はい、まさに。 HashMap.get(Object key)のソースコードを見てください。 key.hashcodeを呼び出して内部ハッシュテーブルの位置(bin)を計算し、その位置の値(ある場合)を返します。

ただし、「手作り」のハッシュコード/等しいメソッドには注意してください。オブジェクトをキーとして使用する場合は、後でハッシュコードが変更されないようにしてください。そうしないと、マッピングされた値が見つかりません。言い換えると、イコールとハッシュコードの計算に使用するフィールドはfinal(またはオブジェクトの作成後は「変更不可」)である必要があります。

String nameおよびString phonenumberとの連絡先があり、両方のフィールドを使用してequals()およびhashcode()を計算するとします。次に、彼の携帯電話番号を使用して「John Doe」を作成し、彼をお気に入りのドーナツショップにマッピングします。 hashcode()は、ハッシュテーブルのインデックス(bin)を計算するために使用され、そこにドーナツショップが格納されます。

これで、彼が新しい電話番号を持っていることがわかり、John Doeオブジェクトの電話番号フィールドを変更します。これにより、新しいハッシュコードが生成されます。そして、このハッシュコードは新しいハッシュテーブルインデックスに解決されます。これは通常、John Doesのお気に入りのドーナツショップが格納されていた位置ではありません。

問題は明らかです。この場合、「特定の電話番号のJohn Doe」ではなく、「John Doe」をドーナツショップにマッピングします。したがって、不要なフィールドを使用してHashMapsとHashSetsに問題を引き起こす可能性があるため、自動生成されたequals/hashcodeが本当に必要なものであることを確認するように注意する必要があります。

編集2

HashSetにオブジェクトを追加する場合、オブジェクトは内部ハッシュテーブルのキーであり、値は設定されますが未使用です(Objectの静的インスタンスのみ)。 openjdk 6(b17)からの実装は次のとおりです。

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;

public boolean add(E e) {
  return map.put(e, PRESENT)==null;
}
14
Andreas_D

HashMapHashSetなどのハッシュコンテナは、コンテンツを「バケット」に分割することで、コンテナに格納されている要素に高速にアクセスできます。

たとえば、数値のリスト:Listに格納された1, 2, 3, 4, 5, 6, 7, 8は、メモリ内で(概念的には)[1, 2, 3, 4, 5, 6, 7, 8]のようになります。

同じ数のセットをSetに格納すると、次のようになります:[1, 2] [3, 4] [5, 6] [7, 8]。この例では、リストは4つのバケットに分割されています。

ここで、ListSetの両方から値6を検索したいとします。リストでは、リストの最初から始めて、6に到達するまで各値を確認する必要があります。これには6つのステップが必要です。セットで正しいバケットを見つけたら、そのバケット内の各アイテム(この例では2つのみ)をチェックして、これを3ステップのプロセスにします。このアプローチの価値は、データが多いほど劇的に増加します。

しかし、どのバケットを確認すればよいのでしょうか。これがhashCodeメソッドの出番です。アイテムを探すバケットを決定するには、JavaハッシュコンテナーがhashCodeを呼び出し、結果に関数を適用します。この関数は、可能な最高速のルックアップのためのバケットとアイテムの数。

ルックアップ中に正しいバケットが見つかると、そのバケット内の各アイテムがリストのように一度に1つずつ比較されます。そのため、hashCodeをオーバーライドする場合は、equalsもオーバーライドする必要があります。したがって、任意のタイプのオブジェクトがequalsメソッドとhashCodeメソッドの両方を持っている場合は、MapのキーまたはSetのエントリとして使用できます。これらのメソッドを正しく実装するために従う必要のある規約があります。これに関する正規のテキストは、Josh Blochのすばらしい本であるEffective Java: Item 8:equalsをオーバーライドするときに常にhashCodeをオーバーライドする

5
Tendayi Mawushe

HashMap/HashSetの@Override public int hashcode()の重要性は何ですか?

これにより、マップのインスタンスは、マップのコンテンツに応じて有用なハッシュコードを生成できます。同じ内容の2つのマップは同じハッシュコードを生成します。内容が異なる場合は、ハッシュコードが異なります。

このハッシュコードはどこで内部的に使用されていますか?

決して。このコードは存在するだけなので、マップを別のマップのキーとして使用できます。

_myMap<someObject, Object>_のように、someObjectではなくStringに対して値をマッピングできますか?

はい、ただしsomeObjectはオブジェクトではなくクラスである必要があります(名前はオブジェクトを渡したいことを示しています。型を参照していることを明確にするためにSomeObjectにする必要があります)。

これが成功するために私が従う必要のあるすべての契約は何ですか?

クラスはhashCode()およびequals()を実装する必要があります。

[編集]

キーのハッシュコード(チェック!)は、ハッシュテーブルで値がマップされる実際のものであると言っていますか?

はい。

5
Aaron Digulla

はい。 HashMapでは、任意のオブジェクトをキーとして使用できます。そのためには、以下の手順に従ってください。

  1. オーバーライドが等しい。

  2. HashCodeをオーバーライドします。

両方のメソッドの規約は、Java.lang.Objectのドキュメントに非常に明確に記載されています。 http://Java.Sun.com/javase/6/docs/api/Java/lang/Object.html

はい、hashCode()メソッドはHashMapによって内部的に使用されるため、適切な値を返すことがパフォーマンスにとって重要です。

これがHashMapのhashCode()メソッドです

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

上記のコードから明らかなように、各キーのhashCodeは、マップのhashCode()だけでなく、キーと値のペアを配置するバケットの検索にも使用されます。そのため、hashCode()はHashMapのパフォーマンスに関連しています

5
Varun
  1. Java内のObjectにはhashCode()メソッドが必要です。HashMapHashSetは例外ではありません。このハッシュコードハッシュマップ/セットを別のハッシュマップ/セットに挿入する場合に使用されます。
  2. HashMap/HashSetのキーとして、任意のクラスタイプを使用できます。これには、hashCode()メソッドが等しいオブジェクトに対して等しい値を返すこと、およびequals()メソッドがコントラクト(再帰的、推移的、対称的)に従って実装されていることが必要です。 Objectのデフォルトの実装はすでにこれらの規約に従いますが、参照の同等性ではなく値の同等性が必要な場合は、それらをオーバーライドすることができます。
3
Thomas

アーロン・ディグラは完全に正しいです。人々が気づいていないように思われる興味深い追加のメモは、キーオブジェクトのhashCode()メソッドがそのまま使用されないことです。実際には、HashMapによって再ハッシュされます。つまり、hash(someKey.hashCode))を呼び出します。ここで、hash()は内部ハッシュメソッドです。

これを確認するには、ソースを見てください: http://kickjava.com/src/Java/util/HashMap.Java.htm

この理由は、一部の人々はhashCode()をうまく実装しておらず、hash()関数がより良いハッシュ分布を提供するためです。これは基本的にパフォーマンス上の理由で行われます。

2
GaryF

質問2への回答として、Hashmapのキーとして使用できる任意のクラスを使用できますが、ベストプラクティスは、不変クラスをHashMapのキーとして使用することです。または、少なくとも、「hashCode」および「equals」の実装がクラスの一部の属性に依存している場合は、これらの属性を変更するメソッドを提供しないように注意する必要があります。

2
sateesh

Equals()、hashcode()とハッシュテーブルの間には、一般にJava(および.NETについても))の複雑な関係があります。ドキュメントから引用するには:

public int hashCode()

オブジェクトのハッシュコード値を返します。このメソッドは、_Java.util.Hashtable_によって提供されるハッシュテーブルなどの利点のためにサポートされています。

HashCodeの一般的な規約は次のとおりです。

  • Javaアプリケーションの実行中に同じオブジェクトで複数回呼び出される場合は常に、オブジェクトの等値比較で使用される情報が変更されない限り、hashCodeメソッドは常に同じ整数を返す必要があります。この整数は、あるアプリケーションの実行から同じアプリケーションの別の実行まで一貫性を保つ必要はありません。
  • Equals(Object)メソッドに従って2つのオブジェクトが等しい場合、2つのオブジェクトのそれぞれでhashCodeメソッドを呼び出すと、同じ整数の結果が生成される必要があります。
  • Equals(_Java.lang.Object_)メソッドに従って2つのオブジェクトが等しくない場合、2つのオブジェクトのそれぞれでhashCodeメソッドを呼び出すと、異なる整数の結果が生成される必要はありません。ただし、プログラマは、異なるオブジェクトに対して異なる整数の結果を生成すると、ハッシュテーブルのパフォーマンスが向上する可能性があることに注意する必要があります。

合理的な限り、クラスhashCodeで定義されているObjectメソッドは、個別のオブジェクトに対して個別の整数を返します。 (これは通常、オブジェクトの内部アドレスを整数に変換することによって実装されますが、この実装手法はJava™プログラミング言語では必要ありません。)

この線

_@Overrides public int hashCode()
_

hashCode()メソッドがオーバーライドされることを通知するだけです。このia 通常HashMapのキーとしてタイプを使用しても安全であることを示す記号。

そして、はい、HashMapequals()hashCode()の規約に従う任意のオブジェクトをキーとして簡単に使用できます。

2
Joey

HashSet、HashTable、HashMapなどのコレクションクラスのHashCodeメソッド–ハッシュコードは、ハッシュの目的でサポートされているオブジェクトの整数を返します。オブジェクトの内部アドレスを整数に変換することで実装されます。ハッシュコードメソッドは、equalsメソッドをオーバーライドするすべてのクラスでオーバーライドする必要があります。 HashCodeメソッドの3つの一般的な連絡先

  • 2つの等しいオブジェクトに対してequalメソッドに、両方のオブジェクトのHashCodeを呼び出すと、同じ整数値が生成されます。

  • 単一のオブジェクトに対して複数回呼び出されている場合、定数の整数値を返す必要があります。

  • 2つの等しくないオブジェクトに対してacc。等しいメソッドに、次に両方のオブジェクトに対してHashCodeメソッドを呼び出す場合、異なる値を生成する必要はありません。

0
LearnerJava