GetHashCode() の戻り値は、同じ文字列値が使用されていると仮定して一貫性が保証されていますか? (C#/ASP.NET)
今日、コードをサーバーにアップロードしましたが、驚いたことに、サーバー (win2008 64 ビット) がデスクトップ コンピューターとは異なる値を返すため、一部のデータのインデックスを再作成する必要がありました。
私が間違っていなければ、GetHashCode は同じ値で一貫性がありますが、フレームワークの異なるバージョン間で一貫性があるとは限りません。
String.GetHashCode() に関する MSDN ドキュメントから:
GetHashCode の動作はその実装に依存しており、共通言語ランタイムのバージョンによって異なる場合があります。これが発生する理由は、GetHashCode のパフォーマンスを向上させるためです。
String.GetHashCodeに依存する情報をデータベーステーブルに入力するという同様の問題がありました(最良のアイデアではありません)。作業中のサーバーをx64にアップグレードすると、String.GetHashCodeから取得していた値がすでに表にあるものと矛盾しています。私の解決策は、x86 フレームワークで String.GetHashCode と同じ値を返す独自のバージョンの GetHashCode を使用することでした。
コードは次のとおりです。「安全でないコードを許可する」でコンパイルすることを忘れないでください。
/// <summary>
/// Similar to String.GetHashCode but returns the same as the x86 version of String.GetHashCode for x64 and x86 frameworks.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static unsafe int GetHashCode32(string s)
{
fixed (char* str = s.ToCharArray())
{
char* chPtr = str;
int num = 0x15051505;
int num2 = num;
int* numPtr = (int*)chPtr;
for (int i = s.Length; i > 0; i -= 4)
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
if (i <= 2)
{
break;
}
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
numPtr += 2;
}
return (num + (num2 * 0x5d588b65));
}
}
実装はフレームワークのバージョンに依存しますが、アーキテクチャにも依存します。string.GetHashCode() の実装は、フレームワークの x86 バージョンと x64 バージョンでは、バージョン番号が同じであっても異なります。
サーバーと自宅のコンピューターの両方が同じバージョンの.NETを実行していると確信しているため、32ビットと64ビットのオペレーティングシステムに違いがあるのではないかと思います。
私は常にGetHashCode()の使用にうんざりしていたので、自分のハッシュアルゴリズムを単純にロールするのは良い考えかもしれません。少なくとも、そのために.aspxページのインデックスをすばやく作成することになりました。
ジョナスがうまく答えた質問への直接的な回答ではありませんが、ハッシュでの等価性テストについて心配している場合は、これが役立つ場合があります
私たちのテストから、ハッシュコードで必要なものに応じて、C# ではハッシュコードは等価操作で一意である必要はありません。例として、次のことを考慮してください。
equals 演算子をオーバーロードする必要があったため、オブジェクトが揮発性でステートレスになり、データから直接ソースを取得したため、オブジェクトの GetHashCode 関数をオーバーロードする必要があったため、アプリケーションのある場所でオブジェクトが表示されるようにする必要がありました。同じ参照である場合だけでなく、同じ data から取得された場合、別のオブジェクトと等しいと見なされます。一意のデータ識別子は Guid です。
equals 演算子は、(null をチェックした後) レコードの Guid をチェックしただけなので、簡単に対応できました。
残念ながら、HashCode のデータ サイズ (int) はオペレーティング システムに依存し、32 ビット システムでは、ハッシュコードは 32 ビットになります。数学的には、GetHashCode 関数をオーバーライドすると、32 ビットを超える GUID から一意のハッシュコードを生成することは不可能です (逆から見てください。32 ビット整数を GUID に変換するにはどうすればよいでしょうか?)。
次に、Guid を文字列として取得し、Guid の HashCode を返すいくつかのテストを行いました。これは、ほとんどの場合、テストで一意の識別子を返しますが、常にではありません。
ただし、オブジェクトがハッシュされたコレクション オブジェクト (ハッシュテーブル、辞書など) にある場合、2 つのオブジェクトが一意ではないがそれらのハッシュコードが一意である場合、ハッシュコードは最初のオプション ルックアップとしてのみ使用されます。 -一意のハッシュ コードが使用されている場合、等値演算子は常に等値を決定するためのフォールバックとして使用されます。
私が言ったように、これはあなたの状況に関連するかもしれないし、関連しないかもしれませんが、もしそうなら、それは便利なヒントです.
アップデート
実証するために、Hashtable があります。
キー: オブジェクト A (ハッシュコード 1)、値 オブジェクト A1
キー:オブジェクト B (ハッシュコード 1)、値 オブジェクト B1
キー: オブジェクト C (ハッシュコード 1)、値 オブジェクト C1
キー:オブジェクト D (ハッシュコード 2)、値 オブジェクト D1
キー:オブジェクト E (ハッシュコード 3)、値 オブジェクト E1
オブジェクト A のキーを持つオブジェクトのハッシュテーブルを呼び出すと、ハッシュコード 1 の呼び出しの 2 つの手順の後、オブジェクト A1 が返されます。次に、ハッシュコード 1 を持つ一意のキーがないため、キー オブジェクトの等価性チェックが行われます。
オブジェクト D のキーを使用してオブジェクトのハッシュテーブルを呼び出すと、1 ステップのハッシュ ルックアップの後にオブジェクト D1 が返されます。
デスクトップとして Win2008 x86 を実行していますか? Win2008 には、Vista RTM に含まれる 2.0 の更新バージョンであるバージョン2.0.50727.1434が含まれているためです。
ただし、オブジェクトがハッシュされたコレクション オブジェクト (ハッシュテーブル、辞書など) にある場合、2 つのオブジェクトが一意ではないがそれらのハッシュコードが一意である場合、ハッシュコードは最初のオプション ルックアップとしてのみ使用されます。 - 一意のハッシュ コードが使用されている場合、等値演算子は、等値を決定するためのフォールバックとして常に使用されます。
これがハッシュルックアップの仕組みですよね?各バケットには、同じハッシュ コードを持つアイテムのリストが含まれています。
したがって、これらの条件下で正しいアイテムを見つけるために、値の等価比較を使用した線形検索が行われます。
また、ハッシュの実装が適切な分散を実現する場合、この検索は必要ありません (つまり、バケットごとに 1 つのアイテム)。
私の理解は正しいですか?
/// <summary>
/// Default implementation of string.GetHashCode is not consistent on different platforms (x32/x64 which is our case) and frameworks.
/// FNV-1a - (Fowler/Noll/Vo) is a fast, consistent, non-cryptographic hash algorithm with good dispersion. (see http://isthe.com/chongo/tech/comp/fnv/#FNV-1a)
/// </summary>
private static int GetFNV1aHashCode(string str)
{
if (str == null)
return 0;
var length = str.Length;
// original FNV-1a has 32 bit offset_basis = 2166136261 but length gives a bit better dispersion (2%) for our case where all the strings are equal length, for example: "3EC0FFFF01ECD9C4001B01E2A707"
int hash = length;
for (int i = 0; i != length; ++i)
hash = (hash ^ str[i]) * 16777619;
return hash;
}
この実装は、以前に投稿された安全でない実装よりも遅くなる可能性があります。しかし、はるかに簡単で安全です。
私は言わなければならないでしょう...あなたはそれに頼ることはできません。たとえば、c# の md5 ハッシュ コードを使用して file1 を実行し、同じファイルを新しいディレクトリにコピー アンド ペーストすると、同じファイルであっても、ハッシュ コードが異なって表示されます。明らかに、同じ .net バージョンで、すべて同じです。変わったのはパスだけです。