web-dev-qa-db-ja.com

HashMapでStringキーを使用するのは悪い考えですか?

Stringクラスの hashCode() メソッドはnotであり、異なるString-sに対して一意のハッシュコードを生成することが保証されていることを理解しています。 HashMap-sに文字列キーを配置する(デフォルトのString hashCode()メソッドを使用して)多くの使用法を見ています。マップputが以前に明確に異なる文字列キーでマップに配置されたHashMapエントリを置き換えた場合、この使用法の多くは重大なアプリケーションの問題を引き起こす可能性があります。

String.hashCode()が異なるString-sに対して同じ値を返すシナリオに遭遇する可能性はどのくらいですか?キーが文字列の場合、開発者はこの問題をどのように回避しますか?

66
Marcus Leon

開発者は、プログラムの正確性を実現するために、HashMapのハッシュ衝突の問題を回避する必要はありません。

ここで理解すべき重要な点がいくつかあります。

  1. 衝突はハッシュの固有の機能であり、そうする必要があります。可能な値(あなたの場合は文字列ですが、他の型にも適用されます)の数は、整数の範囲よりもはるかに大きいです。
  2. ハッシュのすべての使用には衝突を処理する方法があり、Javaコレクション(HashMapを含む)も例外ではありません。
  3. ハッシュは平等テストに関係しません。等しいオブジェクトは等しいハッシュコードを持たなければならないのは事実ですが、その逆は真実ではありません。多くの値が同じハッシュコードを持つことになります。そのため、ハッシュコード比較を同等の代替として使用しないでください。コレクションにはありません。ハッシュを使用してサブコレクション(Javaコレクションワールド)でバケットと呼ばれます)を選択しますが、.equals()を使用して実際に同等性をチェックします。
  4. コレクションで衝突が発生して不正な結果を引き起こすことを心配する必要がないだけでなく、ほとんどのアプリケーションでは、パフォーマンスを心配する必要もありません-Java hashed Collections do aハッシュコードを管理する非常に良い仕事。
  5. さらに良いことに、(キーとしての文字列)について尋ねた場合、Javaの文字列クラスはかなり良いハッシュコードを生成するので、ハッシュコード自体を心配する必要さえありません。したがって、提供されているほとんどのJavaクラス。

必要に応じて、さらに詳細に説明します。

ハッシュの仕組み(特に、JavaのHashMapのようなハッシュされたコレクションの場合、これはあなたが尋ねたものです)はこれです:

  • HashMapは、バケットと呼ばれるサブコレクションのコレクションに、指定した値を保存します。これらは実際にはリンクリストとして実装されます。これらの制限された数があります:iirc、デフォルトで開始する16、およびマップに項目を追加するにつれて数が増加します。値よりも常にバケットが多いはずです。 1つの例を提供するために、デフォルトを使用して、HashMapに100個のエントリを追加すると、256個のバケットがあります。

  • マップでキーとして使用できるすべての値は、ハッシュコードと呼ばれる整数値を生成できる必要があります。

  • HashMapはこのハッシュコードを使用してバケットを選択します。最終的に、これは整数値moduloバケット数を取得することを意味しますが、その前に、JavaのHashMapには内部メソッド(hash()と呼ばれる)があります。凝集。

  • 値を検索するとき、HashMapはバケットを選択し、.equals()を使用してリンクリストの線形検索により個々の要素を検索します。

そのため、正確さのために衝突を回避する必要はありません。通常、パフォーマンスのために衝突を心配する必要はありません。また、ネイティブのJavaクラス(Stringなど) 、ハッシュコード値の生成について心配する必要もありません。

独自のハッシュコードメソッドを記述する必要がある場合(つまり、名/姓のペアのような複合値を持つクラスを記述したことを意味する)、事態はやや複雑になります。ここで間違っている可能性は十分にありますが、ロケット科学ではありません。最初に、これを知ってください:正確さを保証するためにあなたがしなければならないことは、等しいオブジェクトが等しいハッシュコードを生成することを保証することです。したがって、クラスのhashcode()メソッドを記述する場合、equals()メソッドも記述する必要があり、それぞれの同じ値を調べる必要があります。

悪いが正しいhashcode()メソッドを書くことは可能です。つまり、「等しいオブジェクトは等しいハッシュコードを生成する必要があります」という制約を満たしますが、多くの衝突があるため、パフォーマンスは非常に悪くなります。

これの標準的な縮退最悪の場合は、すべての場合に単純に定数値(3など)を返すメソッドを記述することです。これは、すべての値が同じバケットにハッシュされることを意味します。

それでもworkになりますが、パフォーマンスはリンクリストのパフォーマンスに低下します。

明らかに、このようなひどいhashcode()メソッドを書くことはありません。適切なIDEを使用している場合は、IDEを生成できます。 StackOverflowはコードが大好きなので、上記のfirstname/lastnameクラスのコードを次に示します。


public class SimpleName {
    private String firstName;
    private String lastName;
    public SimpleName(String firstName, String lastName) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((firstName == null) ? 0 : firstName.hashCode());
        result = prime * result
                + ((lastName == null) ? 0 : lastName.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SimpleName other = (SimpleName) obj;
        if (firstName == null) {
            if (other.firstName != null)
                return false;
        } else if (!firstName.equals(other.firstName))
            return false;
        if (lastName == null) {
            if (other.lastName != null)
                return false;
        } else if (!lastName.equals(other.lastName))
            return false;
        return true;
    }
}

113
CPerkins

HashMap.put メソッドは、 String.hashCode を見ただけではキーが同じかどうかを判断しないと強く思います。

ハッシュ衝突 の可能性は間違いなくあるので、 String.equals メソッドも呼び出されて- String sは、2つのStringsがhashCodeから返された同じ値を持つ場合が実際にある場合、本当に等しいです。

したがって、新しいキーStringは、 String によって返される値が等しく、かつ HashMapである場合にのみ、hashCodeに既に存在するものと同じキーequalsと判断されます。 メソッドはtrueを返します。

また、追加するには、この考えはString以外のクラスにも当てはまります。これは、 Object クラス自体に hashCode および equals メソッドが既にあるためです。

編集

したがって、質問に答えるには、いいえ、StringのキーにHashMapを使用することは悪い考えではありません。

4
coobird

これは問題ではなく、ハッシュテーブルの仕組みです。整数よりもはるかに明確な文字列があるため、すべての明確な文字列に明確なハッシュコードを設定することは不可能です。

他の人が書いたように、ハッシュの衝突はequals()メソッドを介して解決されます。これが引き起こす唯一の問題は、ハッシュテーブルの縮退であり、パフォーマンスの低下につながります。 JavaのHashMapが 負荷係数 を持っているのはこのためです。これは、バケットと挿入された要素の比率であり、超過すると、バケット数が2倍のテーブルの再ハッシュを引き起こします。

これは通常非常にうまく機能しますが、ハッシュ関数が良好な場合、つまり特定の入力セットに対して統計的に予想される衝突数を超えない場合にのみ有効です。 String.hashCode()はこの点で優れていますが、常にそうであるとは限りませんでした。 申し立て 、Java 1.2より前)は、n番目の文字ごとに含まれています。これは高速でしたが、n番目の文字を共有するすべての文字列で予測可能な衝突を引き起こしました。あなたがそのような通常の入力をするのに十分に不運であるか、誰かがあなたのアプリにDOS攻撃をしたいならば、悪いです。

4

あなたはハッシュ衝突について話している。ハッシュの衝突は、hashCodeされるタイプに関係なく問題です。 hashCode(HashMapなど)を使用するすべてのクラスは、ハッシュ衝突をうまく処理します。たとえば、HashMapはバケットごとに複数のオブジェクトを保存できます。

HashCodeを自分で呼び出す場合を除き、心配する必要はありません。ハッシュの衝突はまれですが、何も壊しません。

2
Keith Randall