ハッシュコードでのJavaのメソッドに深い思考

序文

ゴーを学ぶ最近の言語では、囲碁の言語は、ポインタオブジェクト、メモリアドレス値へのポインタ変数のポイントがあります。C言語学習類人猿の友人は、ポインタの概念を知っている必要があります。C言語の構文にゴーゴー言語はポインタが正常である持っているので、クラスCプログラミング言語であると言うことができ、同様です。私たちは、アドレスの文字を取ることができます&前に、使用される変数の変数の対応するメモリアドレスを提供します。

package main

import "fmt"

func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */
   fmt.Printf("a 变量的地址是: %x\n", &a  )

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}
复制代码

それは主に開発言語は、Javaですので、私は、Javaがポインタではないと思いますので、その後、Javaはどのように変数それのメモリアドレスを取得しますか?

あなたは、変数のメモリアドレスを取得することができれば、我々は明確に2つのオブジェクトが同じオブジェクトであるかどうかを知ることができる2つのオブジェクトが等しい場合、メモリアドレスが同じオブジェクトである疑いが異なるオブジェクトとその逆です。

多くの人々は、返されたオブジェクトのhashCodeメソッドは、第5章では、私のコンテンツを含む、オブジェクトのメモリアドレスであると言う、「Javaコアプログラミング、ボリュームはI」も、その値は、オブジェクトのメモリアドレスでのhashCode見つけられると言われています。

しかし、hashCodeメソッドは、実際のメモリアドレスはありますか?この質問に答える前に、のは、いくつかの基本を復習しましょう。

==とequals

2つのオブジェクトが主を通じてJavaで等しいかどうかを比較==メモリにその記憶アドレスを比較し、数字。Javaオブジェクトのクラスは、クラスが書き換えオブジェクトされていない場合、すべてのクラスのスーパークラスは、デフォルトを継承しているequalsことで、その後、方法をequals2つのオブジェクトが同じであるかどうかを判断することができる方法、それは内部を通じてあるので、==達成しました。

//Indicates whether some other object is "equal to" this one.
public boolean equals(Object obj) {
    return (this == obj);
}
复制代码

ヒント:ここでは疑い追加説明

私たちは学習Javaは、Javaの単一継承が継承されていることを知っていた場合、すべてのクラスはObjectクラスを継承する場合は、他のクラスを拡張することができたときに、なぜクラスを作成しますか?

キーワードによって作成されたクラスがなけれとき、これは、直接的および間接的な継承に承継の問題を伴うextend>オブジェクト-あなたは指定されたクラスの表示、デフォルトのクラスを直接継承したオブジェクト、Aを継承しますが。キーワードによってクラスを作成する場合extend> B - - >オブジェクト指定されたクラスのディスプレイを継承し、それが間接的にObjectクラス、Aを継承します 。

ここでは、同じである比較の2つのオブジェクトが同じオブジェクトであれば、それは、平等のメモリ内のアドレスであるということです。そして、時には我々は同じ、つまりクラスは独自の論理「等しい」という概念を持っているが、彼らは同じオブジェクトを指しているかどうか知りたいのない2つのオブジェクトの内容を比較する必要があります。

例えば、2つの文字列が比較として同じである場合String a = "Hello"String b = new String("Hello")、2つのケースが存在する場合およびBが同じオブジェクト(同じメモリアドレス)であるか否かを比較するために、同じで、その含有量は全く同じですか?それを区別する方法の特定のニーズ?

あなたが使用している場合==、それは彼らが、メモリ内の同じオブジェクトですが、デフォルトの親クラスStringオブジェクトがオブジェクトであるため、デフォルトかどうかを比較することであるequals方法は、メモリアドレスを比較することで、私たちは書き換えする必要がequals書かれた文字列のソースコードのような方法を。

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
复制代码

私たちは時になるようにa == baとbが同じオブジェクトであるかどうかを判断した場合、a.equals(b)コンテンツのより多くのAとBが同じであることは、それが十分に理解されなければなりませんさ。

Stringクラス以上でJDKは、基本的な書き換えもフロート、equalsメソッドと同様に、データ型の整数、ロング、ダブルをオーバーライドするequals方法を。あなたが平等のためにそれらを比較したい場合は、我々は時間のビジネスや整数パラメータを行うので、ロングコードで使用し、使用することを覚えておく必要があるequals代わりに、方法を==

使用のため==番号が予期しないピットは、データの多くのタイプは、パッケージ内の定数プールであるように、発生有し、例えばIntegerCache、LongCacheが好き。時間直接定数プールからのデータ値の特定の範囲内で利用可能になると、新しいオブジェクトを作成しない場合。

あなたが使用したい場合は==、後で、あなたがそれまでに、基本的な型としてこれらのパックのデータ型を変換することができ==、基本的なタイプから、比較することにより、==変換プロセスで値を比較するが、NPE(NullPointException)を注意して発生します。

ハッシュコードのオブジェクト

この方法は、等しい回収容器内のオブジェクトかどうかを見つけるために使用することができ、通常は実質的に照会されるオブジェクトとオブジェクトのコレクション内の各要素を取得するために一つずつある2つのコンテンツオブジェクトを比較することができる等しくequalsAで見つかった場合に、比較をあなたが平等を探しているオブジェクトの要素は、結果を比較停止し、継続するための方法に等しい場合、検索は肯定的なメッセージを返し、そうでなければ、それは負のメッセージを返します。

しかし、この比較の方法により、非常に低い効率で、時間の複雑性は比較的高いです。我々は次に、いくつかの方法で符号化することができ、各オブジェクトは、特定のコード値を有し、コード値に応じて、次にグループに異なる領域にオブジェクト、我々は必要なときにそのように、コレクション内のオブジェクト、我々最初のオブジェクトがコードに基づいて、オブジェクトの値によって、そのゾーンに、その領域に格納されていると判断することができるequals比較内容等しい方法であり、オブジェクトがコレクション内に存在するかどうかを知ることができます。

このように、我々はまた、クエリの時間を削減しながら、クエリの効率を最適化するために比較したクエリの数を減らします。

ハッシュコードは、オブジェクト・クラスを変更ネイティブネイティブメソッドでデフォルトの方式を定義するJavaでのこの符号化方法は、返される値は、int型のものです。

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * ...
 * As much as is reasonably practical, the hashCode method defined by
 * class {@code Object} does return distinct integers for distinct
 * objects. (This is typically implemented by converting the internal
 * address of the object into an integer, but this implementation
 * technique is not required by the
 * Java™ programming language.)
 *
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode
 */
public native int hashCode();
复制代码

ノートは説明から知ることができる、hashCodeメソッドは、このオブジェクトのハッシュコード値を返します。HashMapのハッシュテーブルを好きに有益であろう。クラスオブジェクトに定義されているハッシュコードメソッドは、異なるオブジェクトの異なる整数値を返します。ローカル混乱異議はThis is typically implemented by converting the internal address of the object into an integer整数値にオブジェクトの内部アドレスを変換することで、通常の状況下で達成する方法を意味し、この1、。

あなたはそれが下のリターンに到達するとは思われない場合は、メモリアドレスの目的である、我々は、その実装を見て継続することができますが、それがネイティブメソッドであるため、私たちは、直接内部が達成される方法をここで見ることができません。あなたがソースコードを見たい場合は、Javaは、OpenJDKのJREまたは他のオープンソースは、対応するC / C ++コードで見つけることができ、OracleのJDKが見えない、完全なソースコードをダウンロードするだけで、JDK、非ネイティブメソッド自体達成します。私たちは、OpenJDKの中を見つけるObject.cあなたはhashCodeメソッドが指す見ることができ、ファイルJVM_IHashCodeを処理する方法。

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};
复制代码

JVM_IHashCode実装される方法jvm.cppは次のように定義されます。

JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))  
  JVMWrapper("JVM_IHashCode");  
  // as implemented in the classic virtual machine; return 0 if object is NULL  
  return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;  
JVM_END 
复制代码

ここで三眼式は、真の値が計算ハッシュコードにより得られるObjectSynchronizer :: FastHashCode、その具体的な実現synchronizer.cpp、採取キーコードフラグメントの一部。

intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
  if (UseBiasedLocking) {
  
  ......
  
  // Inflate the monitor to set hash code
  monitor = ObjectSynchronizer::inflate(Self, obj);
  // Load displaced header and check it has hash code
  mark = monitor->header();
  assert (mark->is_neutral(), "invariant") ;
  hash = mark->hash();
  if (hash == 0) {
    hash = get_next_hash(Self, obj);
    temp = mark->copy_set_hash(hash); // merge hash code into header
    assert (temp->is_neutral(), "invariant") ;
    test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
    if (test != mark) {
      // The only update to the header in the monitor (outside GC)
      // is install the hash code. If someone add new usage of
      // displaced header, please update this code
      hash = test->hash();
      assert (test->is_neutral(), "invariant") ;
      assert (hash != 0, "Trivial unexpected object/monitor header usage.");
    }
  }
  // We finally get the hash
  return hash;
}
复制代码

上記のコードからのhashCodeの実際の計算で見つけることができますされget_next_hash、私たちは、この文書検索に残っているget_next_hash彼のキーコードを取得し、。

static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
     // This form uses an unguarded global Park-Miller RNG,
     // so it's possible for two threads to race and generate the same RNG.
     // On MP system we'll have lots of RW access to a global, so the
     // mechanism induces lots of coherency traffic.
     value = os::random() ;
  } else
  if (hashCode == 1) {
     // This variation has the property of being stable (idempotent)
     // between STW operations.  This can be useful in some of the 1-0
     // synchronization schemes.
     intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else
  if (hashCode == 2) {
     value = 1 ;            // for sensitivity testing
  } else
  if (hashCode == 3) {
     value = ++GVars.hcSequence ;
  } else
  if (hashCode == 4) {
     value = cast_from_oop<intptr_t>(obj) ;
  } else {
     // Marsaglia's xor-shift scheme with thread-specific state
     // This is probably the best overall implementation -- we'll
     // likely make this the default in future releases.
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;
  }

  value &= markOopDesc::hash_mask;
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;
}
复制代码

get_next_hash我々は0カウントから始まる、そして、ここでハッシュ値を計算するプログラムの6種類がある場合、デフォルトで最後の公式があるなど一連の乱数を増やすから様々な方法で、関連付けられたメモリアドレス、方法があることがわかります種、すなわち、乱数生成。多分、我々は、ハッシュコードを見ることができますし、メモリアドレスが関連しているが、直接ではなく、仮想マシンのバージョンや設定を確認するには、特定の必要性のメモリアドレスの代わりに。

和のhashCodeに等しいです

オブジェクトクラスは対等であり、hashCodeメソッドは、印刷ハッシュコードの符号なし16進数の値を含むオブジェクトクラスtoStringメソッドの内容を含む、有しています。

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
复制代码

オブジェクトの内容を比較する必要が、私たちは通常、オーバーライドとしてequalsメソッドを、しかしオーバーライドメソッドもこれまでに、なぜ疑問に思い、hashCodeメソッドをオーバーライドする必要が等しいですか?

あなたは一般的な慣習のhashCodeに違反するそうしない場合ので、クラスの結果は、一緒にコレクションなどのHashMapとHashSetのを含め、適切にハッシュベースのコレクションを、すべての作業を組み合わせることはできません。

ここで一般的な規則、クラスオブジェクトhashCodeメソッドからの注釈は、以下の態様を含む、と理解することができます、

  • アプリケーションの実行中に、限りの情報の比較動作が使用されているオブジェクトのメソッドは、同じオブジェクトに非常に多くのコールを変更されていないに等しいように、hashCodeメソッドは、常に同じ値を返す必要があります。

  • 2つのオブジェクトがequalsメソッドの比較に応じて等しい場合、これら2つのオブジェクトのハッシュコードメソッドを呼び出すと同じ整数の結果を生成しなければなりません。

  • 比較のequalsメソッドに応じて2つのオブジェクトが等しくない場合、2つのハッシュコードメソッドの呼び出し元のオブジェクト、必ずしも必要ではないhashCodeメソッドは、異なる結果を生成しなければなりません。しかし、等しくないオブジェクト異なる整数にハッシュ値を生成するために、ハッシュテーブル(ハッシュ・テーブル)Aの性能を向上させることができます。

hashCodeメソッドをオーバーライドしないequalsメソッドのオーバーライドは、上述した第2の合意に反している場合、理論的に、同じオブジェクトが同じハッシュ値を持つ必要があります

我々は異常な方法を利用したいと思いますしかし、もしルールはみんな暗黙の了解は、何の方法は、それの結果がどうなるか、ハッシュコードは、equalsメソッドをカバーすることに書き直されていないのですか?

私たちは、学生のクラスをカスタマイズし、equalsメソッドをオーバーライドし、私たちはその後、Studentクラスは、デフォルトのhashCodeメソッドは、オブジェクトのスーパークラスを呼び出すことですhashCodeメソッドを呼び出して、hashCodeメソッドをオーバーライドしていなかった、整数の乱数を返しますタイプの値。

public class Student {

    private String name;

    private String gender;

    public Student(String name, String gender) {
        this.name = name;
        this.gender = gender;
    }

    //省略 Setter,Gettter
    
    @Override
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof Student) {
            Student anotherStudent = (Student) anObject;

            if (this.getName() == anotherStudent.getName()
                    || this.getGender() == anotherStudent.getGender())
                return true;
        }
        return false;
    }
}
复制代码

私たちは、次のテストの結果、2つのオブジェクトを作成し、プロパティの値を設定します。

public static void main(String[] args) {

    Student student1 = new Student("小明", "male");
    Student student2 = new Student("小明", "male");

    System.out.println("equals结果:" + student1.equals(student2));
    System.out.println("对象1的散列值:" + student1.hashCode() + ",对象2的散列值:" + student2.hashCode());
}
复制代码

結果が得られ

equals结果:true
对象1的散列值:1058025095,对象2的散列值:665576141
复制代码

私たちは、名前と男女平等内容によって、オブジェクトのプロパティを決定するためにequalsメソッドを書き換え、それはhashCodeメソッドのhashCodeオブジェクトクラスを呼び出しているので、印刷が等しい2つの整数値ではありませんので。

学生のよく知られた原理はHashMapを知っている必要があり、我々はキーとしてオブジェクトを格納するHashMapを使用したオブジェクトは、HashMapのは、配列+リンクリストからなる構造体である場合、それらは同じハッシュコードではないので、この結果は、異なる配列のインデックスにそう私たちは主のクエリ結果に応じて行くときはnullです。

public static void main(String[] args) {

    Student student1 = new Student("小明", "male");
    Student student2 = new Student("小明", "male");
    
    HashMap<Student, String> hashMap = new HashMap<>();
    hashMap.put(student1, "小明");

    String value = hashMap.get(student2);
    System.out.println(value); 
}
复制代码

出力

null
复制代码

我々は確かに我々は、彼らが同じであるべきと信じて、得られた結果に満足していない、とstudent2 STUDENT1、ここでは異なるメモリアドレスしかし、彼らは同じ論理コンテンツを持っています。

これは十分に理解されていない場合は、Studentクラスの猿友達がStringクラスの考え方の下で交換することができる唯一のStringクラスが道ここにハッシュコードをオーバーライドしないequalsメソッドをオーバーライドする場合、私たちはしばしば、Stringクラスは想像し、キー値のHashMapとして使用されています文字列にnew String("s")キーとしてその値をつけたが、その後に基づいてnew String("s")、それは結果を得るヌル取得する時間だ、人々が受け入れることは困難です。

それは理論的または実際のプログラミングに合意されているかどうかので、常にhashCodeメソッドをオーバーライドしている間、我々はequalsメソッドを書き換え、これを覚えておいてください

hashCodeメソッドをオーバーライドしているが、我々は元のクラスオブジェクトにハッシュコードを取得したい場合は、私たちがすることができますがSystem.identityHashCode(Object a)取得し、この方法は、オブジェクト、hashCodeメソッドのデフォルト値hashCodeメソッドを返すオブジェクトが書き換えられても、それは影響を与えません。

public static native int identityHashCode(Object x);
复制代码

概要

ないメモリアドレスハッシュコードが場合、Javaのメモリアドレスは、どのようにそれを入手するには?周りを見て、直接的な方法が利用できました。

その後で、結局、ので、多分これは、Javaはアセンブリ言語やCと機械語への高レベルの言語は、より抽象的で、複雑さを隠蔽しているため、メモリへの直接アクセスが必要に対処しないがあることが、Java言語の作家だと思いますパッケージにさらに基づいて、CおよびC ++。そして、自動ガベージコレクション機構と対象年齢生成問題ので、Javaオブジェクトのアドレスは、実際のメモリアドレスを取得するので、ほとんど意味が変わります。

他の友人がある場合はもちろん、上記のブロガーは、ビューの自分のポイントで異なる見解や意見は、メッセージを残すことができるAPE、私たちは一緒に議論します。


個人公開番号:料理も牛

牛も料理:世間の注目数を引く長押しへようこそ!

分散型の説明と分析は、定期的にあなたのためのマイクロ層のインターネット企業やその他のサービス関連技術を提供します。

おすすめ

転載: juejin.im/post/5d50c1826fb9a06b1777a795