Object.GetHashCode

Мой вопрос может дублировать реализацию по умолчанию для Object.GetHashCode (), но я спрашиваю снова, потому что я не понял принятый ответ на Вон тот.

Для начала у меня есть три вопроса о принятом ответе на предыдущий вопрос, который цитирует некоторую документацию, а именно:

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

Это правда? Мне кажется, что у двух объектов не будет одного и того же хэш-кода, потому что код объекта не используется повторно, пока объект не будет собран в мусор (т.е. больше не существует).

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

Это проблема? Например, я хочу связать некоторые данные с каждым экземпляром узла в дереве DOM. Для этого «узлы» должны иметь идентификатор или хэш-код, чтобы я мог использовать их в качестве ключей в словаре данных. Разве не хэш-код, который идентифицирует, является ли он «одним и тем же объектом», то есть «ссылочным равенством, а не« равенством значений », то я хочу?

«Эта реализация не особенно полезна для хеширования; поэтому производные классы должны переопределять GetHashCode»

Это правда? Если он не подходит для хеширования, то что, если он для чего-нибудь пригоден, и почему он вообще определен как метод Object?


Мой последний (и, возможно, самый важный для меня) вопрос: если я должен изобрести / переопределить реализацию GetHashCode () для произвольного типа, который имеет семантику «ссылочного равенства», это следующая разумная и хорошая реализация:

class SomeType
{
  //create a new value for each instance
  static int s_allocated = 0;
  //value associated with this instance
  int m_allocated;
  //more instance data
  ... plus other data members ...
  //constructor
  SomeType()
  {
    allocated = ++s_allocated;
  }
  //override GetHashCode
  public override int GetHashCode()
  {
    return m_allocated;
  }
}

Изменить

К вашему сведению, я протестировал это, используя следующий код:

    class TestGetHash
    {
        //default implementation
        class First
        {
            int m_x;
        }
        //my implementation
        class Second
        {
            static int s_allocated = 0;
            int m_allocated;
            int m_x;
            public Second()
            {
                m_allocated = ++s_allocated;
            }
            public override int GetHashCode()
            {
                return m_allocated;
            }
        }
        //stupid worst-case implementation
        class Third
        {
            int m_x;
            public override int GetHashCode()
            {
                return 0;
            }
        }

        internal static void test()
        {
            testT<First>(100, 1000);
            testT<First>(1000, 100);
            testT<Second>(100, 1000);
            testT<Second>(1000, 100);
            testT<Third>(100, 100);
            testT<Third>(1000, 10);
        }

        static void testT<T>(int objects, int iterations)
            where T : new()
        {
            System.Diagnostics.Stopwatch stopWatch =
                System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < iterations; ++i)
            {
                Dictionary<T, object> dictionary = new Dictionary<T, object>();
                for (int j = 0; j < objects; ++j)
                {
                    T t = new T();
                    dictionary.Add(t, null);
                }
                for (int k = 0; k < 100; ++k)
                {
                    foreach (T t in dictionary.Keys)
                    {
                        object o = dictionary[t];
                    }
                }
            }
            stopWatch.Stop();
            string stopwatchMessage = string.Format(
                "Stopwatch: {0} type, {1} objects, {2} iterations, {3} msec",
                typeof(T).Name, objects, iterations,
                stopWatch.ElapsedMilliseconds);
            System.Console.WriteLine(stopwatchMessage);
        }
    }

На моей машине результаты / вывод следующие:

First type, 100 objects, 1000 iterations, 2072 msec
First type, 1000 objects, 100 iterations, 2098 msec
Second type, 100 objects, 1000 iterations, 1300 msec
Second type, 1000 objects, 100 iterations, 1319 msec
Third type, 100 objects, 100 iterations, 1487 msec
Third type, 1000 objects, 10 iterations, 13754 msec

Моя реализация занимает половину времени по сравнению с реализацией по умолчанию (но мой тип больше на размер моего члена данных m_allocated).

Моя реализация и реализация по умолчанию масштабируются линейно.

Для сравнения и в качестве проверки работоспособности глупая реализация плохо запускается и еще хуже масштабируется.


person ChrisW    schedule 16.07.2009    source источник
comment
Возможно использование изменчивого объявления и связанных инкременторов для обеспечения безопасности потоков.   -  person Cecil Has a Name    schedule 16.07.2009
comment
Будет ли вообще иметь значение, если будут случайные случайные столкновения из-за того, что он не является потокобезопасным? Я думал, что реализация GetHashCode не обязательно должна гарантировать уникальные возвращаемые значения для разных объектов, и что вместо этого достаточно, если они в основном уникальны.   -  person ChrisW    schedule 16.07.2009
comment
Очень интересно увидеть, что ваша простая реализация значительно увеличивает производительность поиска за счет 4 Байт / экземпляр. Интересный компромисс.   -  person ToolmakerSteve    schedule 10.02.2018


Ответы (3)


Наиболее важное свойство, которое должна иметь реализация хэш-кода:

Если два объекта сравниваются как равные, они должны иметь одинаковые хэш-коды.

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

Если вы написали класс, который реализует собственное равенство, отличное от ссылочного равенства, то вам НЕОБХОДИМО переопределить GetHashCode таким образом, чтобы два объекта, которые сравниваются как равные, имели одинаковые хэш-коды.

Теперь вы можете сделать это, просто возвращая каждый раз ноль. Это была бы паршивая хэш-функция, но это было бы законно.

Другие свойства хороших хеш-функций:

  • GetHashCode никогда не должен вызывать исключение

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

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

  • Объекты, которые не сравниваются как равные, должны иметь разные хэш-коды, хорошо распределенные по всему диапазону 32-битного целого числа.

person Eric Lippert    schedule 16.07.2009
comment
Было бы правильно или слишком оптимистично читать это как объект по умолчанию. GetHashCode - хорошая реализация GetHashCode для типов, которые используют ссылочное равенство, то есть для классов и / или интерфейсов (но не структур), для которых вы не переопределили object.Equals; где «хорошая реализация» означает хорошую / быструю производительность при использовании этих типов в качестве ключа словаря. - person ChrisW; 17.07.2009
comment
Да, реализация GetHashCode по умолчанию, которую мы вам предлагаем, довольно хороша. - person Eric Lippert; 17.07.2009
comment
Цель хорошего алгоритма хеширования - повысить производительность поиска. Одно это может быть лучшим ответом, который может получить ОП. - person Trap; 03.08.2011

Вопрос:

Это правда? Мне кажется, что у двух объектов не будет одного и того же хэш-кода, потому что код объекта не используется повторно, пока объект не будет собран в мусор (т.е. больше не существует).

Два объекта могут использовать один и тот же хэш-код, если он сгенерирован по умолчанию реализацией GetHashCode, потому что:

  1. Результат GetHashCode по умолчанию не должен изменяться во время существования объекта, и реализация по умолчанию обеспечивает это. Если бы это могло измениться, такие типы, как Hashtable, не смогли бы справиться с этой реализацией. Это потому, что ожидается, что хэш-код по умолчанию - это хеш-код уникального идентификатора экземпляра (даже если такого идентификатора нет :)).
  2. Диапазон значений GetHashCode - это целое число (2 ^ 32).

Заключение. Достаточно выделить 2 ^ 32 объекта с сильными ссылками (это должно быть легко в Win64), чтобы достичь предела.

Наконец, есть явное утверждение в ссылка на объект.GetHashCode в MSDN: реализация метода GetHashCode по умолчанию не гарантирует уникальных возвращаемых значений для разных объектов. Кроме того, .NET Framework не гарантирует реализацию метода GetHashCode по умолчанию, и значение, которое он возвращает, будет одинаковым для разных версий .NET Framework. Следовательно, реализация этого метода по умолчанию не должна использоваться в качестве уникального идентификатора объекта для целей хеширования.

person Alex Yakunin    schedule 16.07.2009

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

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

person Kenan E. K.    schedule 16.07.2009
comment
Что плохого в раздаче? Если для сопоставления хэшей с ведрами вы разделите хеш на количество сегментов и используете остаток, мне кажется, что моя реализация распределяет объекты одинаково / равномерно по всем сегментам. - person ChrisW; 16.07.2009
comment
Это было бы правдой, если бы это был алгоритм отображения хэш-корзины. См., Например, concentric.net/~Ttwang/tech/inthash.htm . Цитата: Для хеш-функции распределение должно быть равномерным. Это означает, что когда результат хеширования используется для вычисления адреса хеш-сегмента, все сегменты будут выбраны с одинаковой вероятностью. Кроме того, одинаковые хеш-ключи должны быть хешированы для получения очень разных хеш-результатов. В идеале изменение одного бита в ключе хеширования должно влиять на все биты результата хеширования. - person Kenan E. K.; 17.07.2009