Как мне привязать long к int в hashCode()?
У меня есть ряд объектов, у которых есть поле long
, значение которого однозначно идентифицирует конкретный объект во всей моей системе, подобно GUID. Я использовал Object.equals()
для использования этого идентификатора для сравнения, потому что я хочу, чтобы он работал с копиями объекта. Теперь я также хочу переопределить Object.hashCode()
, что в основном означает сопоставление моего long
с некоторым возвращаемым значением int
.
Если я правильно понял цель hashCode
, она в основном используется в хеш-таблицах, поэтому было бы желательно равномерное распределение. Это означает, что достаточно просто вернуть id % 2^32
. Это все, или я должен знать о чем-то еще?
Ответы
Ответ 1
Так как Java 8 можно использовать
Long.hashCode(guid);
Для более старых версий Java вы можете использовать следующее:
Long.valueOf(guid).hashCode();
Обратите внимание, что это решение создает новый объект для стека, а первый - нет (хотя вполне вероятно, что Java оптимизирует создание объекта.)
Глядя на документы, оба способа используют только следующий алгоритм:
(int)(this.longValue()^(this.longValue()>>>32))
Это достойные решения, поскольку они используют библиотеку Java - всегда лучше использовать что-то, что уже было протестировано.
Ответ 2
Это немного незначительная вещь, если вы уже не используете Guava, но Guava может сделайте это для вас красиво:
public int hashCode() {
return Longs.hashCode(id);
}
Это дает вам эквивалент Long.valueOf(id).hashCode()
:
return (int) (value ^ (value >>> 32));
Кроме того, если у вас были другие значения или объекты, которые были частью хэш-кода, вы могли бы просто написать
return Objects.hashCode(longValue, somethingElse, ...);
long
будет автобоксирован в long
, поэтому вы получите правильный хэш-код для него как часть общего хэш-кода.
Ответ 3
Вы правильно поняли цель hashCode
. Да, желательно равномерное распределение (хотя это и не фактическое требование).
Я бы предложил ((id >> 32) ^ id)
.
Вышеприведенное выражение:
- Использует все биты исходного значения, не отбрасывает информацию заранее. Например, в зависимости от того, как вы генерируете идентификаторы, верхние биты могут меняться чаще (или наоборот).
- Не вводит никакого смещения в сторону значений с более чем одним (нулями), так как это было бы так, если бы две половины были объединены с операцией OR (AND).
Ответ 4
Java 8 добавляет Long.hashCode(long) в JDK.
Следующий код может обеспечить более высокую производительность. Этот код уменьшает вычисление до 32-разрядного int
вместо вычисления с 64-разрядным long
. Это может повлиять на 32-разрядную и меньшую архитектуры. 32-разрядные процессы на компьютерах x86 могли бы оптимизировать это в одну инструкцию, которая просто регистрирует XORs 2.
return (int)(value ^ (value >>> 32));
Как отмечено в других ответах, это не имеет хороший эффект лавины и, следовательно, может привести к столкновениям, Можно использовать криптографические хеш-функции для обеспечения высокого лавинного эффекта. Однако существуют и другие алгоритмы, такие как Murmur Hash (подробнее информация), которые имеют очень хороший лавинный эффект, но не потребляет столько процессорного времени.
Ответ 5
(l >> 32) ^ l
- хороший хэш-код в большинстве случаев; особенно когда длинные имеют равномерное распределение.
Поскольку это был принятый ответ, я публикую это, чтобы прояснить некоторые из моих комментариев о том, когда он НЕ хороший хэш-код надолго.
В примере, который я дал, был класс Point следующим образом:
public class Point {
private final long coords; //x in high-bits, y in low
public int getX() {
return (int)(coords >> 32);
}
public int getY() {
return (int)coords;
}
public int hashCode() {
return (int)((coords >> 32) ^ (coords));
}
}
Это может показаться надуманным, но иногда у вас есть несколько "полей", упакованных в длинный.
Итак, поле coords
представляет 32 бита x и 32 бит y. Так почему же это проблема? Ну, это не так, если каждый из x и y равномерно распределен по их соответствующим 32 битам. Но это маловероятно на практике. Скорее всего, X и Y ограничены некоторым числом. Скажем 1024, так как это 2 ^ 10. Это означает, что не более 10 младших бит каждого X и Y установлены:
00000000 00000000 000000XX XXXXXXXX 00000000 00000000 000000YY YYYYYYYY
Возможны комбинации 2 ^ 20 (1024 * 1024). Но что делает операция hashCode?
00000000 00000000 000000XX XXXXXXXX
^ 00000000 00000000 000000YY YYYYYYYY
-------------------------------------
= 00000000 00000000 000000?? ????????
Существует не более 2 ^ 10 (1024) возможных значений hashCode, так как только младшие 10 бит могут быть чем-то другим, кроме нуля. Отношение хэш-значений к реальным значениям составляет 1024:(1024*1024)
или 1:1024
. Таким образом, сразу с места битвы существует вероятность 1/1024, что два числа имеют одинаковый хэш.
Теперь позвольте рассчитать вероятность столкновения, применив математику из проблемы рождения. Пусть p (n) - вероятность того, что с n значениями будет хотя бы одно столкновение. Мы знаем, что p (1025+) = 1, так как существует только 1024 значения.
p(n) = 1 - (n! * (1024 choose n))/1024^n
Это работает следующим образом:
n: p(n)
1: 0.00000
2: 0.00098
3: 0.00293
4: 0.00585
5: 0.00973
6: 0.01457
...
38: 0.50096
...
79: 0.95444
...
148: 0.99999
Только с 38 элементами, возможно, происходит столкновение. С 148 пунктами вероятность 99.999% (хотя бы одного) столкновения. При использовании 148 предметов каждый предмет имеет 7% -ный шанс столкнуться с другим предметом. Имея надлежащую хэширующую функцию, беря знания домена, эти числа могут легко перейти к 0.
Другими словами, знание вашего домена и то, как все происходит на практике, являются ключом к созданию хэша-исполнителя. Функции библиотеки стараются как можно лучше выполнять работу, не зная ничего о вашем домене и быть опытными, как правило, полагаются на распределение данных, которое не будет происходить на практике.
Ответ 6
int result = (int)((longVal >> 32) ^ longVal);
будет более хорошо распределен, потому что modulo не будет возвращать другое значение, если изменились только верхние биты вашего длинного значения.