Может ли object.GetHashCode() давать разные результаты для одних и тех же объектов (строк) на разных машинах?

Возможно ли, чтобы один и тот же объект, в частности string или любого примитивного или очень простого типа (например, struct), при вызове на разных машинах производил разные значения метода .GetHashCode()?

Например, возможно ли, чтобы выражение "Hello World".GetHashCode() давало другое значение на другой машине. В первую очередь я прошу C#.NET, но я полагаю, что это может относиться к Java или даже к другим языкам?

Редактировать:

Как видно из ответов и комментариев ниже, мне известно, что .GetHashCode() можно переопределить, и нет никакой гарантии результата, который он дает между разными версиями фреймворка. Поэтому важно уточнить, что я имею в виду простые типы (которые не могут быть унаследованы, поэтому GetHashCode() должны быть переопределены) и я использую одни и те же версии фреймворка на всех машинах.


person Ivaylo Slavov    schedule 12.01.2012    source источник
comment
Да, согласно документации.   -  person Cody Gray    schedule 12.01.2012
comment
Для вашего удовольствия -- blogs.msdn.com/b/ericlippert/archive/2011/02/28/   -  person Austin Salonen    schedule 12.01.2012
comment
Разница существует только для строк между x86 и 64. Найдите различные возвращаемые значения для строк на x86 и x64 в ссылке, предоставленной @CodyGray   -  person digEmAll    schedule 12.01.2012
comment
@IvayloSlavov: при проверке кода в функции GetHashCode() на наличие строк значения приводятся к указателям, которые имеют разные размеры на машинах x86 и x64, поэтому я думаю, что это причина, по которой вы получаете разные значения. Можете ли вы подтвердить, что 2 машины используют 2 разные архитектуры?   -  person digEmAll    schedule 12.01.2012
comment
@digEmAll, я не могу, на самом деле мой вопрос был скорее гипотетическим, чем подкрепленным реальным кодом. Я думаю о способе реализации внутренней балансировки нагрузки в распределенном приложении Windows — у меня есть служба, развернутая на разных локальных серверах, и мне нужен хороший и стабильный хэш-код (для разных машин и стартапов), чтобы я мог определить конкретную службу. для запуска строковым токеном. И я считал встроенный хэш, вероятно, самым быстрым и простым способом, но, похоже, я ошибался.   -  person Ivaylo Slavov    schedule 12.01.2012
comment
@IvayloSlavov: Если это ваше требование, то GetHashCode абсолютно не подходит для использования. Используйте GetHashCode только для одной цели: для балансировки хеш-таблицы. Если вам нужен хеш-код для какой-то другой цели, напишите собственный алгоритм хэш-кода, подходящий для этой цели.   -  person Eric Lippert    schedule 12.01.2012
comment
@EricLippert Спасибо, я уже рассматриваю это как единственный вариант. Спасибо за быстрые и точные ответы!   -  person Ivaylo Slavov    schedule 12.01.2012
comment
@IvayloSlavov: чтобы получить стабильное хеширование строк, используйте методы и классы пространства имен: System.Security.Cryptography. Например, вы можете использовать MD5   -  person digEmAll    schedule 12.01.2012
comment
@IvayloSlavov Я сделал подобное с хэшами, которые были под моим контролем, и это сработало хорошо. Вы должны учитывать тот факт, что если вы не можете создать идеальный хеш (погуглите идеальный хеш, если вы не знакомы с этой концепцией), то всегда существует риск коллизии, которую вам нужно обработать. . Тем не менее, если есть строковый токен, идентифицирующий службу, почему бы просто не передать его между машинами? Даже если хеширование используется внутри сервера, у него нет выхода из этого контекста.   -  person Jon Hanna    schedule 13.01.2012


Ответы (2)


Краткий ответ: Да.

Но короткие ответы не забавны, не так ли?

При реализации GetHashCode() вы должны предоставить следующие гарантии:

Когда GetHashCode() вызывается для другого объекта, который следует считать равным этому, в этом домене приложения будет возвращено то же значение.

Вот и все. Есть некоторые вещи, которые вам действительно нужно попытаться сделать (распределите биты с неравными объектами как можно больше, но не занимайте это так долго, чтобы это перевешивало все преимущества хеширования в первую очередь) и ваш код будет отстой, если вы этого не сделаете, но на самом деле он не сломается. Он сломается, если вы не зайдете так далеко, потому что тогда, например:

dict[myObj] = 3;
int x = dict[myObj];//KeyNotFoundException

Хорошо. Если я внедряю GetHashCode(), почему я могу пойти дальше, а почему нет?

Во-первых, почему я не могу?

Возможно, это немного другая версия сборки, которую я улучшал (или, по крайней мере, пытался) между сборками.

Возможно, один из них 32-битный, а другой 64-битный, и я сходил с ума от эффективности и выбрал для каждого свой алгоритм, чтобы использовать разные размеры слов (это не редкость, особенно при хешировании таких объектов, как коллекции или строки) .

Может быть, какой-то элемент, который я решаю учитывать при принятии решения о том, что составляет «равные» объекты, сам варьируется от системы к системе таким образом.

Может быть, я на самом деле намеренно ввожу другое семя с разными сборками, чтобы поймать любой случай, когда коллега ошибочно зависит от моего хэш-кода! (Я слышал, что MS делает это со своей реализацией для string.GetHashCode(), но не могу вспомнить, слышал ли я это из надежного или доверчивого источника).

В основном, хотя, это будет одна из первых двух причин.

Теперь, почему я могу дать такую ​​гарантию?

Скорее всего, если и сделаю, то случайно. Если элемент можно сравнить на равенство только на основе одного целочисленного идентификатора, то это то, что я собираюсь использовать в качестве своего хэш-кода. Все остальное будет больше работы для менее хорошего хэша. Я вряд ли изменю это, так что я мог бы.

Другая причина, по которой я мог бы это сделать, заключается в том, что я сам хочу получить эту гарантию. Нечего сказать, что я не могу это предоставить, просто мне не нужно.


Хорошо, давайте перейдем к чему-нибудь практическому. Есть случаи, когда вам может потребоваться независимая от машины гарантия. Есть случаи, когда вы можете хотеть противоположного, к чему я скоро вернусь.

Во-первых, проверьте свою логику. Можете ли вы справиться с столкновениями? Хорошо, тогда мы начнем.

Если это ваш собственный класс, то реализуйте так, чтобы обеспечить такую ​​гарантию, задокументируйте его, и все готово.

Если это не ваш класс, то реализуйте IEqualityComparer<T> таким образом, чтобы обеспечить его. Например:

public class ConsistentGuaranteedComparer : IEqualityComparer<string>
{
  public bool Equals(string x, string y)
  {
    return x == y;
  }
  public int GetHashCode(string obj)
  {
    if(obj == null)
      return 0;
    int hash = obj.Length;
    for(int i = 0; i != obj.Length; ++i)
      hash = (hash << 5) - hash + obj[i];
    return hash;
  }
}

Затем используйте это вместо встроенного хэш-кода.

Есть интересный случай, когда мы можем хотеть обратного. Если я могу управлять набором строк, которые вы хэшируете, то я могу выбрать кучу строк с одним и тем же хеш-кодом. Производительность вашей коллекции на основе хэша будет в худшем случае и будет довольно ужасной. Скорее всего, я смогу продолжать делать это быстрее, чем вы справитесь с этим, так что это может быть атакой типа «отказ в обслуживании». Это происходит не во многих случаях, но важным является случай, когда вы обрабатываете XML-документы, которые я отправляю, и вы не можете просто исключить некоторые элементы (многие форматы допускают свободу элементов внутри них). Тогда NameTable внутри вашего парсера пострадает. В этом случае мы каждый раз создаем новый хэш-механизм:

public class RandomComparer : IEqualityComparer<string>
{
  private int hashSeed = Environment.TickCount;
  public bool Equals(string x, string y)
  {
    return x == y;
  }
  public int GetHashCode(string obj)
  {
    if(obj == null)
      return 0;
    int hash = hashSeed + obj.Length;
    for(int i = 0; i != obj.Length; ++i)
      hash = hash << 5 - hash + obj[i];
    hash += (hash <<  15) ^ 0xffffcd7d;
    hash ^= (hash >>> 10);
    hash += (hash <<   3);
    hash ^= (hash >>>  6);
    hash += (hash <<   2) + (hash << 14);
    return hash ^ (hash >>> 16)
  }
}

Это будет согласовано в пределах данного использования, но не согласовано от использования к использованию, поэтому злоумышленник не может создать входные данные, чтобы заставить их быть DoS-отказом. Между прочим, NameTable не использует IEqualityComparer<T>, потому что он хочет иметь дело с массивами символов с индексами и длинами без создания строки без необходимости, но он делает что-то подобное.

Кстати, в Java хеш-код для string указан и не изменится, но это может быть не так для других классов.

Изменить: проведя некоторое исследование общего качества подхода, использованного в ConsistentGuaranteedComparer выше, я больше не доволен наличием таких алгоритмов в своих ответах; хотя он служит для описания концепции, он не имеет такого хорошего распределения, как хотелось бы. Конечно, если такую ​​штуку уже реализовали, то изменить без нарушения гарантии не получится, но если бы я сейчас рекомендовал использовать эта моя библиотека, написанная после указанного исследования следующим образом:

public class ConsistentGuaranteedComparer : IEqualityComparer<string>
{
  public bool Equals(string x, string y)
  {
    return x == y;
  }
  public int GetHashCode(string obj)
  {
    return obj.SpookyHash32();
  }
}

Это для RandomComparer выше не так плохо, но его тоже можно улучшить:

public class RandomComparer : IEqualityComparer<string>
{
  private int hashSeed = Environment.TickCount;
  public bool Equals(string x, string y)
  {
    return x == y;
  }
  public int GetHashCode(string obj)
  {
    return obj.SpookyHash32(hashSeed);
  }
}

Или для еще большей предсказуемости:

public class RandomComparer : IEqualityComparer<string>
{
  private long seed0 = Environment.TickCount;
  private long seed1 = DateTime.Now.Ticks;
  public bool Equals(string x, string y)
  {
    return x == y;
  }
  public int GetHashCode(string obj)
  {
    return obj.SpookyHash128(seed0, seed1).GetHashCode();
  }
}
person Jon Hanna    schedule 12.01.2012
comment
Это выходит далеко за рамки того, о чем я просил. Я очень рад получить эту информацию и очень ценю ваши усилия. Спасибо - person Ivaylo Slavov; 12.01.2012
comment
Как я уже сказал, короткие ответы не интересны :) - person Jon Hanna; 13.01.2012
comment
Я искал информацию о реализации хеширования в NameTable, задаваясь вопросом, почему она отличается от GetHashCode(), и этот ответ охватывает это. Хорошо сделано! - person Clay; 22.12.2015
comment
Я слышал, что MS делает это со своей реализацией для string.GetHashCode()... Теперь, когда доступен исходный код .NET, вы теперь есть авторитетный источник, который показывает, что они действительно используют случайные хешкоды в некоторых сборках, если установлена ​​переменная сборки FEATURE_RANDOMIZED_STRING_HASHING. Кроме того, если это сборка DEBUG, они также делают hash1 ^= ThisAssembly.DailyBuildNumber;, чтобы убедиться, что никто не делает ничего глупого, например, пытается сохранить хэш-значения, - person Scott Chamberlain; 05.09.2016

Он будет давать разные результаты даже на одной и той же машине при разных запусках.

Так что его в принципе можно использовать (и он фактически используется) для проверки чего-то во время текущего запуска программы, но нет смысла его хранить, чтобы потом что-то сверять с ним. Потому что полученное вами число генерируется средой выполнения.

ИЗМЕНИТЬ

Для конкретного случая строки он будет давать одинаковый результат даже на разных машинах, за исключением случая, когда машины имеют разную архитектуру.

person Tigran    schedule 12.01.2012
comment
Можете ли вы уточнить больше. Пожалуйста, примите во внимание пересмотр моего вопроса - я пояснил, что прошу только ненаследуемые простые или примитивные типы. Не могли бы вы также поделиться некоторыми ссылками для получения дополнительной информации? заранее спасибо - person Ivaylo Slavov; 12.01.2012
comment
Это не правда. Для строк вы получите другое значение, только если вы измените платформу (x86 против x64), в противном случае GetHashValue всегда возвращает одно и то же значение. - person digEmAll; 12.01.2012
comment
Все еще неправильно. Та же строка, та же версия фреймворка, тот же результат. Это не правило, что это должно быть так, хотя. - person Jon Hanna; 12.01.2012
comment
Действительно, как написано, это будет сломано. Если бы константная строка давала согласованный хэш-код, а непостоянная — нет, то константная строка имела бы хэш-код, отличный от эквивалентного непостоянного, что было бы недопустимо. - person Jon Hanna; 12.01.2012
comment
@JonHanna: может быть, здесь я неправильно использовал постоянное слово. Строки неизменяемы в C#, поэтому все они константы. В этом был смысл. Другими словами: string Hello будет выдавать один и тот же hashcode на моей и на вашей машине, если у нас будет одинаковая архитектура процессора. - person Tigran; 12.01.2012