ハッシュコードと同等のデバッグ、パフォーマンス


数週間前にthis story on reddit これは、URLクラスをマップのキーとして使用する場合の問題について説明します.これはJavaでhashcode ()メソッドを著しくゆっくりと実装することになります.ネットこの種の状況でこのクラスを使用不能にするURL.残念なことに、これはJava API仕様の一部で、もはや後方互換性を破ることなく固定できません.
我々ができることは、equalsとhashcodeの問題を理解することです.どのように、我々は将来そのような問題を避けることができますか?

どのような問題のURL hashcodeと等しいですか?
これを理解するには‌hashcodeのJavadocを見て、

Compares this URL for equality with another object.

If the given object is not a URL then this method immediately returns false.

Two URL objects are equal if they have the same protocol, reference equivalent hosts, have the same port number on the host, and the same file and fragment of the file.

Two hosts are considered equivalent if both host names can be resolved into the same IP addresses; else if either host name can't be resolved, the host names must be equal without regard to case; or both host names equal to null.

Since hosts comparison requires name resolution, this operation is a blocking operation.


ホスト比較は名前解決を必要とするので、この操作はブロッキング操作です
これは不明かもしれません.単純なコードブロックで明確にしましょう.
System.out.println(new URL("http://localhost/").equals(new URL("http://127.0.0.1/")));
System.out.println(new URL("http://localhost/").hashCode() == new URL("http://127.0.0.1/").hashCode());
出力は以下となります.
true
true
これはlocalhostでかなり簡単かもしれません、しかし、我々がドメインとストリングを比較するならば、同じ(彼らがしばしばそうでない)我々はDNSルックアップをする必要があります.HashCode ()の呼び出しのためだけに行う必要があります.

迅速な回避策
この場合の回避策はURLを避けることです.SunはオリジナルのJVMコードでクラスを深く埋めましたが、ほとんどの目的でURIを使用できます.
例えば、ハッシュコードと同等のコールを変更してURLの代わりにURIを使うと、この結果が得られます.
System.out.println(new URI("http://localhost/").equals(new URI("http://127.0.0.1/")));
System.out.println(new URI("http://localhost/").hashCode() == new URI("http://127.0.0.1/").hashCode());
両方のステートメントについてはfalseを取得します.これはいくつかのユースケースに問題があるかもしれませんが、パフォーマンスの大きな違いです.

より大きな落とし穴
我々が地図キーとしてこれまで使ったすべてがストリングであったならば、我々は元気です.この種のバグは、これらのメソッドを使用するすべての場所で私たちを打つことができます.
  • 集合
  • マップ
  • ストレージ
  • ビジネスロジック
  • しかし、それはより深くなります.HashCodeとEquals Logicを使用して独自のクラスを書くときには、しばしば悪いコードを餌食にすることができます.HashCodeメソッドまたは過度に単純なバージョンの小さなパフォーマンスペナルティは、追跡するのが非常に難しい主要なパフォーマンスペナルティを引き起こすことがありえます.
    例えば、HashCodeメソッドが遅いか不正確であるので、より長いストリーム操作は長期の問題を表すことができます.

    最高のハッシュコード実装
    最もHashCodeメソッドとEqualsメソッドを理解するには、まずいくつかの凡例コードを理解する必要があります.今、私は恐ろしいか古いコードを示しません.これは良いコードですが、最善ではありません.
    public int hashCode() {
        return Objects.hash(id, core, setting, values, sets);
    }
    
    このコードは最初は大丈夫かもしれませんが、そうですか?
    理想的なコードです.
    public int hashCode() {
        return id;
    }
    
    これは、100 %ユニークで正しい高速です.文字通り何もしない理由があります.オブジェクトであるIDの1つの例外があります.その場合、オブジェクトをやりたいかもしれません.代わりにNULLなどで動作するhashcode ( ID )を指定します.

    ハッシュコードは等しくない
    これは、HashCodeの実装を記述する際に留意すべき最も重要なことの一つです.このメソッドは、高速で実行しなければならず、偽の場合に等しい値を持つ必要があります.それは本当の場合には正しくない.
    明確にするために、ハッシュコードは常にこの法則に従わなければなりません.
    assert(obj1.hashCode() != obj2.hashCode() && !obj1.equals(obj2));
    
    つまり、ハッシュコードの結果が異なる場合、オブジェクトは異なる必要があり、等しい値からfalseを返さなければなりません.しかし、逆の場合はそうではありません.
    if(obj1.hashCode() == obj2.hashCode()) {
        if(obj1.equals(obj2)) {
           // this can be false...
        }
    }
    
    ここでの値はパフォーマンスです.HashCodeメソッドは、同等よりもずっと速く実行する必要があります.それはすぐに潜在的に高価な同等の計算とインデックス要素をスキップできるようにする必要があります.

    JPA特別事例
    JPA開発者はHashCodeのハードコーディング値を使用するか、クラスオブジェクトを使用してhashcode ()を生成します.あなたがこれについて考えるまで、これは奇妙なようです.
    データベースにIDを生成させた場合、オブジェクトを保存し、ソースオブジェクトにはもう等しくなりません@NaturalId 注釈とデータ型.しかし、それはデータモデルを変える必要があるでしょう.残念ながら、エンティティークラスには適切な回避策はありません.
    実際、私は、それがあなたのためにHashCodeとEqualsメソッドを生成するので、LMPBOKで経験したJPA開発者の多くの問題があると私は推理します.それらは問題かもしれません.

    これはデバッグに関するブログですか?
    その長いセットアップに関して残念です、しかし、はい、それはよくあります.それで、私はこの序文のすべてをデバッグのより一般的な感覚でこれについて話す必要がありました.一般的なインターフェイスの類似のパラダイムを使用する他の言語についてはこれがtrueであることに注意してください.
    このブログはパフォーマンスの問題から始まり、デバッグのレンズの面を議論したいと思います.多くのプロファイラでは、ハッシュコードメソッドのオーバーヘッドはほとんど目立たないでしょう.しかし、それがとてもしばしば呼び出されて、広範囲にわたる含みがあるので、あなたは結局、影響を感じて、他に非難を投げかけることができます.
    ニージャーク反応は“ダミー”ハッシュコードメソッドを実装し、結果として生じる性能差を見ることです.だけではなく、有効な番号のハードコード番号を返します.
    これはいくつかのケースにとって価値があり、HashCodeメソッドがうまく実行されているトップで述べたような問題を解決することもあります.しかし、それはマップを助けません.HashCodeが同じ値を返す場合、マップ内のキーとして使用すると、HashCodeが提供できるすべてのパフォーマンスの利点を効果的に無効にします.
    HashCodeメソッドが良いかどうやって知るのですか?
    我々はデバッガを使用してそれを把握することができます.ちょうどあなたの地図を点検して、HashCodeメソッドの本当の世界価値の感覚を得るために、いろいろなバケツの間のオブジェクトの配布を見てください.
    コミットのコード検証プロセスがある場合は、HashCodeメソッドの複雑さレベルに規則を定義することを強く推奨します.これは遅いコードが侵入するのを防ぐために非常に低く設定されるべきです.
    しかし、問題は巣作りです.例えば、以前に論じたようなコードについて考えてみましょう.
    public int hashCode() {
        return Objects.hash(id, core, setting, values, sets);
    }
    
    それは短く簡単です.しかし、このコードのパフォーマンスは、すべての場所にすることができます.このメソッドは、すべての内部オブジェクトのHashCodeメソッドを呼び出します.これらの方法は性能に関してはるかに悪いことがある.これについて警戒すべきだ.以前に議論したように、URLのようなJDKクラスでさえ、問題があります.

    TLドクター
    HashCodeとEqualsメソッドを自動生成します.IDEは通常はかなり良いです.彼らは私たちの比較を希望するフィールドを選択するオプションを提供しています.残念ながら、それらは両方のフィールドをHashCodeに適用します.
    時々、これは重要ではありません.多くの場合、このメソッドは、プロファイラの窪みを作るにはあまりに小さいので、問題が発生する場所を「参照」しません.しかし、彼らは我々が最適化すべき広い意味を持っています.
    デバッグはマップを検査して、バケット分布を見ることができます.したがって、我々のHashCodeメソッドがどれくらいうまく行っているか、そして、地図と類似したAPIからより一貫した結果を得るためにそれを調整するべきかどうかの感覚を得ることができます.