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

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

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

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

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

Ответы [ 2 ]

14 голосов
/ 12 января 2012

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

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

При реализации 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)
  }
}

Это будет непротиворечиво в пределах данного использования, но не будет непротиворечивым от использования к использованию, поэтому злоумышленник не может создать ввод, чтобы заставить его быть DoSsed. Между прочим, 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();
  }
}
1 голос
/ 12 января 2012

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

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

EDIT

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

...