Update ru translation

This commit is contained in:
krahets
2026-01-23 00:59:30 +08:00
parent 8071daddaa
commit 19dd60a6be
4 changed files with 579 additions and 108 deletions
+355 -38
View File
@@ -1,60 +1,60 @@
# Хеш-алгоритмы
# Алгоритмы хеширования
В предыдущих двух разделах были рассмотрены принципы работы хеш-таблиц и методы обработки хеш-коллизий. Однако ни открытая адресация, ни цепная адресация **не могут уменьшить возникновение хеш-коллизий, они лишь обеспечивают нормальную работу хеш-таблицы при возникновении коллизий**.
В предыдущих разделах были рассмотрены принципы работы хеш-таблиц и методы обработки хеш-конфликтов. Однако ни открытая, ни цепная адресация не могут уменьшить вероятность возникновения хеш-конфликтов, **они лишь обеспечивают корректную работу хеш-таблицы при их возникновении**.
Если хеш-коллизии возникают слишком часто, производительность хеш-таблицы резко ухудшается. Как показано на рисунке ниже, для хеш-таблицы с цепной адресацией в идеальном случае пары ключ-значение равномерно распределены по всем корзинам, достигая наилучшей эффективности поиска; в худшем случае все пары ключ-значение хранятся в одной корзине, и временная сложность деградирует до $O(n)$.
Если хеш-конфликты происходят слишком часто, производительность хеш-таблицы резко снижается. Как показано на рис. 6.8, для хеш-таблицы с цепной адресацией в идеальном случае пары ключ--значение равномерно распределены по всем корзинам, что обеспечивает наилучшую эффективность поиска. В худшем случае все пары ключ--значение хранятся в одной корзине, и временная сложность повышается до $O(n)$.
![Лучший и худший случаи хеш-коллизий](../assets/hash_collision_best_worst_condition.png)
![Лучший и худший случаи хеш-конфликтов](hash_algorithm.assets/hash_collision_best_worst_condition.png)
**Распределение пар ключ-значение определяется хеш-функцией**. Вспомним этапы вычисления хеш-функции: сначала вычисляется хеш-значение, затем берется остаток от деления на длину массива:
**Распределение пар ключ--значение определяется хеш-функцией**. Вспомним этапы вычисления хеш-функции: сначала вычисляется хеш-значение, затем берется остаток от деления на длину массива.
```shell
index = hash(key) % capacity
```
Рассматривая приведенную выше формулу, когда емкость хеш-таблицы `capacity` фиксирована, **хеш-алгоритм `hash()` определяет выходное значение**, а следовательно, и распределение пар ключ-значение в хеш-таблице.
Из этого выражения видно, что при фиксированной емкости хеш-таблицы `capacity` **алгоритм хеширования** `hash()` **определяет выходное значение**, которое, в свою очередь, определяет распределение пар ключ--значение в хеш-таблице.
Это означает, что для снижения вероятности возникновения хеш-коллизий следует сосредоточить внимание на разработке хеш-алгоритма `hash()`.
Это означает, что для снижения вероятности возникновения хеш-конфликтов следует сосредоточиться на разработке алгоритма хеширования `hash()`.
## Цели хеш-алгоритма
## Цели алгоритма хеширования
Для реализации структуры данных хеш-таблицы, которая является "быстрой и стабильной", хеш-алгоритм должен обладать следующими характеристиками.
Для создания быстрой и надежной структуры данных хеш-таблицы алгоритм хеширования должен обладать следующими характеристиками.
- **Детерминированность**: для одного и того же входа хеш-алгоритм всегда должен выдавать один и тот же выход. Только так можно обеспечить надежность хеш-таблицы.
- **Высокая эффективность**: процесс вычисления хеш-значения должен быть достаточно быстрым. Чем меньше вычислительные затраты, тем выше практичность хеш-таблицы.
- **Равномерное распределение**: хеш-алгоритм должен обеспечивать равномерное распределение пар ключ-значение в хеш-таблице. Чем равномернее распределение, тем ниже вероятность хеш-коллизий.
- **Детерминированность**: для одинакового ввода алгоритм хеширования должен всегда давать одинаковый вывод. Это необходимо для обеспечения надежности работы хеш-таблицы.
- **Высокая эффективность**: процесс вычисления хеш-значения должен быть достаточно быстрым. Чем меньше вычислительные затраты, тем выше практическая ценность хеш-таблицы.
- **Равномерное распределение**: алгоритм хеширования должен обеспечивать равномерное распределение пар ключ--значение в хеш-таблице. Чем равномернее распределение, тем ниже вероятность хеш-конфликтов.
На самом деле хеш-алгоритмы помимо реализации хеш-таблиц широко применяются и в других областях.
На практике алгоритмы хеширования применяются не только для реализации хеш-таблиц, но и в других областях.
- **Хранение паролей**: для защиты паролей пользователей система обычно не хранит пароли в открытом виде, а хранит их хеш-значения. Когда пользователь вводит пароль, система вычисляет хеш-значение введенного пароля и сравнивает его с сохраненным хеш-значением. Если они совпадают, пароль считается правильным.
- **Проверка целостности данных**: отправитель данных может вычислить хеш-значение данных и отправить его вместе с данными; получатель может заново вычислить хеш-значение полученных данных и сравнить его с полученным хеш-значением. Если они совпадают, данные считаются целостными.
- **Хранение паролей**: для защиты паролей пользователей система обычно не хранит пароли в открытом виде, а сохраняет их хеш-значения. Когда пользователь вводит пароль, система вычисляет его хеш-значение и сравнивает с сохраненным. Если они совпадают, пароль считается правильным.
- **Проверка целостности данных**: отправитель данных может вычислить хеш-значение данных и отправить его вместе с данными. Получатель может заново вычислить хеш-значение полученных данных и сравнить его с полученным. Если они совпадают, данные считаются неизмененными.
Для криптографических приложений, чтобы предотвратить обратное восстановление исходного пароля из хеш-значения и другие виды обратной инженерии, хеш-алгоритм должен обладать более высоким уровнем безопасности.
В криптографических приложениях для предотвращения обратного вычисления исходного пароля из хеш-значения и других видов обратной инженерии алгоритм хеширования должен обладать дополнительными характеристиками.
- **Односторонность**: невозможно восстановить какую-либо информацию о входных данных из хеш-значения.
- **Устойчивость к коллизиям**: должно быть крайне сложно найти два разных входа, дающих одинаковое хеш-значение.
- **Эффект лавины**: небольшое изменение входа должно приводить к значительному и непредсказуемому изменению выхода.
- **Необратимость**: невозможность извлечь какую-либо информацию о входных данных из хеш-значения.
- **Устойчивость к коллизиям**: должно быть крайне сложно найти два различных входа, дающих одинаковое хеш-значение.
- **Эффект лавины**: небольшие изменения на входе должны приводить к значительным и непредсказуемым изменениям на выходе.
Обратите внимание, что **"равномерное распределение" и "устойчивость к коллизиям" являются двумя независимыми понятиями**, удовлетворение равномерному распределению не обязательно означает устойчивость к коллизиям. Например, при случайном входе `key` хеш-функция `key % 100` может давать равномерно распределенный выход. Однако этот хеш-алгоритм слишком прост, все `key` с одинаковыми последними двумя цифрами дают одинаковый выход, поэтому мы можем легко восстановить подходящий `key` из хеш-значения и таким образом взломать пароль.
Следует отметить, что **«равномерное распределение» и «устойчивость к коллизиям»** -- это два независимых понятия, и выполнение одного из них не обязательно означает выполнение другого. Например, хеш-функция `key % 100` при случайном вводе значения `key` может давать равномерное распределение. Однако этот алгоритм хеширования слишком прост, и все ключи с одинаковыми последними двумя цифрами будут иметь одинаковый вывод, что позволяет легко извлечь пригодные ключи из хеш-значения и взломать пароль.
## Разработка хеш-алгоритма
## Разработка алгоритма хеширования
Разработка хеш-алгоритма — это сложная задача, требующая учета многих факторов. Однако для некоторых нетребовательных сценариев мы также можем разработать простые хеш-алгоритмы.
Создание хеш-алгоритмов представляет собой сложную задачу, требующую учета множества факторов. Однако для некоторых несложных сценариев можно разработать простые хеш-алгоритмы.
- **Аддитивное хеширование**: складываются ASCII-коды каждого символа входа, полученная сумма используется как хеш-значение.
- **Мультипликативное хеширование**: используя независимость умножения, на каждом шаге умножается на константу, ASCII-коды различных символов накапливаются в хеш-значении.
- **XOR-хеширование**: каждый элемент входных данных накапливается в хеш-значении через операцию XOR.
- **Ротационное хеширование**: ASCII-код каждого символа накапливается в хеш-значении, перед каждым накоплением выполняется операция вращения хеш-значения.
- **Аддитивный хеш**: складываются ASCII-коды каждого символа входных данных, полученная сумма используется в качестве хеш-значения.
- **Мультипликативный хеш**: используя свойство некоррелированности умножения, на каждом шаге значение хеша умножается на константу, и в результат добавляется ASCII-код очередного символа.
- **Хеш с использованием операции XOR**: каждый элемент входных данных накапливается в хеш-значении с помощью операции XOR.
- **Ротационный хеш**: ASCII-коды каждого символа накапливаются в хеш-значении, при этом перед каждым накоплением выполняется операция ротации хеш-значения.
```src
[file]{simple_hash}-[class]{}-[func]{rot_hash}
```
Можно заметить, что последним шагом каждого хеш-алгоритма является взятие остатка от деления на большое простое число $1000000007$, чтобы обеспечить нахождение хеш-значения в подходящем диапазоне. Стоит задуматься, почему важно брать остаток от деления именно на простое число, или каковы недостатки взятия остатка от деления на составное число? Это интересный вопрос.
Можно заметить, что последним шагом в каждом из хеш-алгоритмов является взятие остатка от деления на большое простое число $1000000007$, чтобы гарантировать, что хеш-значение находится в допустимом диапазоне. Интересно, почему акцент делается на взятии остатка от деления именно на простое число, и какие недостатки могут быть при делении на составное число?
Сначала дадим вывод: **использование большого простого числа в качестве модуля может максимально обеспечить равномерное распределение хеш-значений**. Поскольку простое число не имеет общих делителей с другими числами, это может уменьшить периодические паттерны, возникающие из-за операции взятия остатка, тем самым избегая хеш-коллизий.
Ответ: **использование большого простого числа в качестве модуля позволяет обеспечить максимально равномерное распределение хеш-значений**. Поскольку простое число не имеет общих делителей с другими числами, это позволяет уменьшить периодические закономерности, возникающие из-за операции взятия остатка, и избежать хеш-конфликтов.
Приведем пример: предположим, мы выбираем составное число $9$ в качестве модуля, оно делится на $3$, тогда все `key`, делящиеся на $3$, будут отображаться в три хеш-значения: $0$, $3$, $6$.
Например, если выбрать в качестве модуля составное число $9$, которое делится на $3$, то все ключи, делящиеся на $3$, будут отображаться в хеш-значения $0$, $3$ и $6$:
$$
\begin{aligned}
@@ -64,7 +64,7 @@ $$
\end{aligned}
$$
Если входные `key` как раз удовлетворяют такому арифметическому распределению данных, то хеш-значения будут группироваться, усиливая хеш-коллизии. Теперь предположим, что `modulus` заменяется на простое число $13$. Поскольку между `key` и `modulus` нет общих делителей, равномерность выходных хеш-значений значительно улучшится.
Если входные ключи имеют такую арифметическую прогрессию, то хеш-значения будут сгруппированы, что умножит хеш-конфликты. Теперь если заменить `modulus` на простое число $13$, то, поскольку между ключами и модулем нет общих делителей, равномерность распределения хеш-значений значительно улучшится:
$$
\begin{aligned}
@@ -74,20 +74,337 @@ $$
\end{aligned}
$$
Следует отметить, что если можно гарантировать, что `key` распределены случайно и равномерно, то выбор простого или составного числа в качестве модуля не имеет значения, оба могут выдавать равномерно распределенные хеш-значения. Однако когда распределение `key` имеет определенную периодичность, взятие остатка от составного числа с большей вероятностью приводит к группировке.
Следует отметить, что если ключи распределены случайно и равномерно, то выбор простого или составного числа в качестве модуля не имеет значения -- оба варианта обеспечат равномерное распределение хеш-значений. Однако при наличии периодичности в распределении ключей использование составного числа в качестве модуля может привести к кластеризации.
В целом мы обычно выбираем простое число в качестве модуля, причем это простое число должно быть достаточно большим, чтобы максимально устранить периодические паттерны и повысить надежность хеш-алгоритма.
В общем случае выбирается простое число в качестве модуля, и это простое число должно быть достаточно большим, чтобы максимально устранить периодические закономерности и повысить устойчивость хеш-алгоритма.
## Распространенные хеш-алгоритмы
Нетрудно заметить, что представленные выше простые хеш-алгоритмы довольно "хрупкие" и далеки от достижения целей разработки хеш-алгоритмов. Например, поскольку сложение и XOR удовлетворяют коммутативному закону, аддитивное хеширование и XOR-хеширование не могут различать строки с одинаковым содержимым, но разным порядком, что может усилить хеш-коллизии и вызвать некоторые проблемы безопасности.
Нетрудно заметить, что описанные выше простые хеш-алгоритмы довольно хрупкие и далеки от достижения целей создания хеш-алгоритмов. Например, сложение и операция XOR удовлетворяют коммутативному закону, поэтому соответствующие хеш-алгоритмы не различают строки с одинаковым содержанием, но разным порядком символов, что может усилить хеш-конфликты и вызвать некоторые проблемы с безопасностью.
На практике обычно используются стандартные хеш-алгоритмы, такие как MD5, SHA-1, SHA-2 и SHA-3. Они могут отображать входные данные произвольной длины в хеш-значения фиксированной длины.
За последнее столетие хеш-алгоритмы находятся в процессе постоянного совершенствования и оптимизации. Часть исследователей стремится повысить производительность хеш-алгоритмов, другая часть исследователей и хакеров пытается найти проблемы безопасности хеш-алгоритмов. В таблице ниже представлены распространенные хеш-алгоритмы, используемые на практике.
На протяжении почти ста лет хеш-алгоритмы постоянно обновляются и оптимизируются. Одни исследователи стремятся повысить производительность, другие исследователи и хакеры сосредоточены на поиске проблем с безопасностью. В табл. 6.2 представлены распространенные хеш-алгоритмы, используемые в реальных приложениях.
- MD5 и SHA-1 неоднократно подвергались успешным атакам, поэтому они отвергнуты различными приложениями безопасности.
- SHA-256 из серии SHA-2 является одним из самых безопасных хеш-алгоритмов, для него до сих пор не было успешных атак, поэтому он часто используется в различных приложениях и протоколах безопасности.
- SHA-3 по сравнению с SHA-2 имеет меньшие затраты на реализацию и более высокую вычислительную эффективность, но в настоящее время его охват использования не так широк, как у серии SHA-2.
- В MD5 и SHA-1 были обнаружены многочисленные уязвимости, поэтому они не используются в сценариях, в которых требуется высокий уровень безопасности.
- SHA-256 из серии SHA-2 является одним из самых безопасных хеш-алгоритмов, до сих пор не было обнаружено ни одной уязвимости, поэтому он часто используется в различных приложениях и протоколах безопасности.
- SHA-3 имеет меньшие затраты на реализацию и более высокую вычислительную эффективность по сравнению с SHA-2, но в настоящее время его использование не так широко распространено, как серии SHA-2.
<p align="center"> Таблица <id
<p align="center"> Таблица <id> &nbsp; Распространенные хеш-алгоритмы </p>
| | MD5 | SHA-1 | SHA-2 | SHA-3 |
| -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- |
| Год появления | 1992 | 1995 | 2002 | 2008 |
| Длина вывода | 128 бит | 160 бит | 256/512 бит | 224/256/384/512 бит |
| Хеш-конфликты | Много | Много | Мало | Мало |
| Уровень безопасности | Низкий, есть известные уязвимости | Низкий, есть известные уязвимости | Высокий | Высокий |
| Применение | Устарел, но еще используется для проверки целостности данных | Устарел | Проверка транзакций в криптовалюте, цифровые подписи и т. д. | Может использоваться в качестве замены SHA-2 |
## Хеш-значения для структур данных
Ключи в хеш-таблице могут быть представлены в виде целых чисел, дробей или строк. Языки программирования обычно предоставляют встроенные хеш-алгоритмы для своих типов данных, чтобы вычислять индексы корзин в хеш-таблице. Например, в Python можно вызвать функцию `hash()` для вычисления хеш-значений для различных типов данных.
- Хеш-значение целых чисел и булевых величин совпадает с их значением.
- Хеш-значение дробных чисел и строк вычисляется по более сложному алгоритму, заинтересованные читатели могут изучить его самостоятельно.
- Хеш-значение кортежа получается путем хеширования каждого элемента и объединения этих хеш-значений в одно.
- Хеш-значение объекта генерируется на основе его адреса в памяти. Путем переопределения метода хеширования объекта можно реализовать генерацию хеша на основе его содержимого.
!!! tip
Обратите внимание, что в разных языках программирования встроенные функции вычисления хеш-значений определяются и реализуются по-разному.
=== "Python"
```python title="built_in_hash.py"
num = 3
hash_num = hash(num)
# Хеш-значение целого числа 3 равно 3
bol = True
hash_bol = hash(bol)
# Хеш-значение булевой величины True равно 1
dec = 3.14159
hash_dec = hash(dec)
# Хеш-значение дробного числа 3.14159 равно 326484311674566659
str = "Hello 算法"
hash_str = hash(str)
# Хеш-значение строки "Hello 算法" равно 4617003410720528961
tup = (12836, "小哈")
hash_tup = hash(tup)
# Хеш-значение кортежа (12836, '小哈') равно 1029005403108185979
obj = ListNode(0)
hash_obj = hash(obj)
# Хеш-значение объекта <ListNode object at 0x1058fd810> равно 274267521
```
=== "C++"
```cpp title="built_in_hash.cpp"
int num = 3;
size_t hashNum = hash<int>()(num);
// Хеш-значение целого числа 3 равно 3
bool bol = true;
size_t hashBol = hash<bool>()(bol);
// Хеш-значение булевой величины 1 равно 1
double dec = 3.14159;
size_t hashDec = hash<double>()(dec);
// Хеш-значение дробного числа 3.14159 равно 4614256650576692846
string str = "Hello 算法";
size_t hashStr = hash<string>()(str);
// Хеш-значение строки "Hello 算法" равно 15466937326284535026
// В C++ встроенная функция std:hash() предоставляет только вычисление хеш-значений базовых типов данных
// Для массивов и объектов нужно реализовывать вычисление хеш-значений самостоятельно
```
=== "Java"
```java title="built_in_hash.java"
int num = 3;
int hashNum = Integer.hashCode(num);
// Хеш-значение целого числа 3 равно 3
boolean bol = true;
int hashBol = Boolean.hashCode(bol);
// Хеш-значение булевой величины true равно 1231
double dec = 3.14159;
int hashDec = Double.hashCode(dec);
// Хеш-значение дробного числа 3.14159 равно -1340954729
String str = "Hello 算法";
int hashStr = str.hashCode();
// Хеш-значение строки "Hello 算法" равно -727081396
Object[] arr = { 12836, "小哈" };
int hashTup = Arrays.hashCode(arr);
// Хеш-значение массива [12836, 小哈] равно 1151158
ListNode obj = new ListNode(0);
int hashObj = obj.hashCode();
// Хеш-значение объекта узла utils.ListNode@7dc5e7b4 равно 2110121908
```
=== "C#"
```csharp title="built_in_hash.cs"
int num = 3;
int hashNum = num.GetHashCode();
// Хеш-значение целого числа 3 равно 3;
bool bol = true;
int hashBol = bol.GetHashCode();
// Хеш-значение булевой величины true равно 1;
double dec = 3.14159;
int hashDec = dec.GetHashCode();
// Хеш-значение дробного числа 3.14159 равно -1340954729;
string str = "Hello 算法";
int hashStr = str.GetHashCode();
// Хеш-значение строки "Hello 算法" равно -586107568;
object[] arr = [12836, "小哈"];
int hashTup = arr.GetHashCode();
// Хеш-значение массива [12836, 小哈] равно 42931033;
ListNode obj = new(0);
int hashObj = obj.GetHashCode();
// Хеш-значение объекта узла 0 равно 39053774;
```
=== "Go"
```go title="built_in_hash.go"
// Go не предоставляет встроенную функцию hash code
```
=== "Swift"
```swift title="built_in_hash.swift"
let num = 3
let hashNum = num.hashValue
// Хеш-значение целого числа 3 равно 9047044699613009734
let bol = true
let hashBol = bol.hashValue
// Хеш-значение булевой величины true равно -4431640247352757451
let dec = 3.14159
let hashDec = dec.hashValue
// Хеш-значение дробного числа 3.14159 равно -2465384235396674631
let str = "Hello 算法"
let hashStr = str.hashValue
// Хеш-значение строки "Hello 算法" равно -7850626797806988787
let arr = [AnyHashable(12836), AnyHashable("小哈")]
let hashTup = arr.hashValue
// Хеш-значение массива [AnyHashable(12836), AnyHashable("小哈")] равно -2308633508154532996
let obj = ListNode(x: 0)
let hashObj = obj.hashValue
// Хеш-значение объекта узла utils.ListNode равно -2434780518035996159
```
=== "JS"
```javascript title="built_in_hash.js"
// JavaScript не предоставляет встроенную функцию hash code
```
=== "TS"
```typescript title="built_in_hash.ts"
// TypeScript не предоставляет встроенную функцию hash code
```
=== "Dart"
```dart title="built_in_hash.dart"
int num = 3;
int hashNum = num.hashCode;
// Хеш-значение целого числа 3 равно 34803
bool bol = true;
int hashBol = bol.hashCode;
// Хеш-значение булевой величины true равно 1231
double dec = 3.14159;
int hashDec = dec.hashCode;
// Хеш-значение дробного числа 3.14159 равно 2570631074981783
String str = "Hello 算法";
int hashStr = str.hashCode;
// Хеш-значение строки "Hello 算法" равно 468167534
List arr = [12836, "小哈"];
int hashArr = arr.hashCode;
// Хеш-значение массива [12836, 小哈] равно 976512528
ListNode obj = new ListNode(0);
int hashObj = obj.hashCode;
// Хеш-значение объекта узла Instance of 'ListNode' равно 1033450432
```
=== "Rust"
```rust title="built_in_hash.rs"
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let num = 3;
let mut num_hasher = DefaultHasher::new();
num.hash(&mut num_hasher);
let hash_num = num_hasher.finish();
// Хеш-значение целого числа 3 равно 568126464209439262
let bol = true;
let mut bol_hasher = DefaultHasher::new();
bol.hash(&mut bol_hasher);
let hash_bol = bol_hasher.finish();
// Хеш-значение булевой величины true равно 4952851536318644461
let dec: f32 = 3.14159;
let mut dec_hasher = DefaultHasher::new();
dec.to_bits().hash(&mut dec_hasher);
let hash_dec = dec_hasher.finish();
// Хеш-значение дробного числа 3.14159 равно 2566941990314602357
let str = "Hello 算法";
let mut str_hasher = DefaultHasher::new();
str.hash(&mut str_hasher);
let hash_str = str_hasher.finish();
// Хеш-значение строки "Hello 算法" равно 16092673739211250988
let arr = (&12836, &"小哈");
let mut tup_hasher = DefaultHasher::new();
arr.hash(&mut tup_hasher);
let hash_tup = tup_hasher.finish();
// Хеш-значение кортежа (12836, "小哈") равно 1885128010422702749
let node = ListNode::new(42);
let mut hasher = DefaultHasher::new();
node.borrow().val.hash(&mut hasher);
let hash = hasher.finish();
// Хеш-значение объекта узла RefCell { value: ListNode { val: 42, next: None } } равно 15387811073369036852
```
=== "C"
```c title="built_in_hash.c"
// C не предоставляет встроенную функцию hash code
```
=== "Kotlin"
```kotlin title="built_in_hash.kt"
val num = 3
val hashNum = num.hashCode()
// Хеш-значение целого числа 3 равно 3
val bol = true
val hashBol = bol.hashCode()
// Хеш-значение булевой величины true равно 1231
val dec = 3.14159
val hashDec = dec.hashCode()
// Хеш-значение дробного числа 3.14159 равно -1340954729
val str = "Hello 算法"
val hashStr = str.hashCode()
// Хеш-значение строки "Hello 算法" равно -727081396
val arr = arrayOf<Any>(12836, "小哈")
val hashTup = arr.hashCode()
// Хеш-значение массива [12836, 小哈] равно 189568618
val obj = ListNode(0)
val hashObj = obj.hashCode()
// Хеш-значение объекта узла utils.ListNode@1d81eb93 равно 495053715
```
=== "Ruby"
```ruby title="built_in_hash.rb"
num = 3
hash_num = num.hash
# Хеш-значение целого числа 3 равно -4385856518450339636
bol = true
hash_bol = bol.hash
# Хеш-значение булевой величины true равно -1617938112149317027
dec = 3.14159
hash_dec = dec.hash
# Хеш-значение дробного числа 3.14159 равно -1479186995943067893
str = "Hello 算法"
hash_str = str.hash
# Хеш-значение строки "Hello 算法" равно -4075943250025831763
tup = [12836, '小哈']
hash_tup = tup.hash
# Хеш-значение кортежа (12836, '小哈') равно 1999544809202288822
obj = ListNode.new(0)
hash_obj = obj.hash
# Хеш-значение объекта узла #<ListNode:0x000078133140ab70> равно 4302940560806366381
```
??? pythontutor "可视化运行"
https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
Во многих языках программирования **только неизменяемые объекты могут использоваться в качестве ключей в хеш-таблице**. Если список (динамический массив) используется в качестве ключа, то при изменении его содержимого хеш-значение также изменится, и мы не сможем найти исходное значение.
Хотя переменные-члены пользовательских объектов (например, узлов связного списка) могут быть изменяемыми, сами объекты можно хешировать. **Это связано с тем**, **что хеш-значение объекта обычно генерируется на основе его адреса в памяти**, и даже если содержимое объекта изменяется, адрес остается неизменным, а значит, и хеш-значение также остается прежним.
Возможно, вы заметили, что при запуске программы в разных окнах выводимые хеш-значения отличаются. **Это связано с тем, что интерпретатор Python при каждом запуске добавляет случайное значение «соли» к функции хеширования строк**. Такой подход эффективно предотвращает атаки типа HashDoS и повышает безопасность хеш-алгоритма.