Плохая идея использовать строковый ключ в HashMap?
Я понимаю, что класс String' hashCode () метод не гарантированный для генерации уникальных хэш-кодов для различных String-s.Я вижу много использования ввода строковых ключей в HashMap-s (используя метод хэш-кода строки по умолчанию ()). Многие из этого использования может привести к значительным проблемам приложений, если карта put переместил запись HashMap, которая ранее была помещена на карту с истинно отличным строковым ключом.
каковы шансы что вы столкнетесь со сценарием, где строка.hashCode () возвращает одно и то же значение для различных строк-s? Как разработчики обходят эту проблему, когда ключ является строкой?
5 ответов:
разработчикам не нужно обходить проблему хэш-коллизий в HashMap для достижения корректности программы.
есть несколько ключевых вещей, чтобы понять здесь:
- конфликты являются неотъемлемой частью хэширования, и они должны быть. Количество возможных значений (строк в вашем случае, но это относится и к другим видам тоже) значительно больше, чем диапазон целых чисел.
- каждый использование хэширования позволяет обрабатывать коллизии, и коллекции Java (включая HashMap) не являются исключением.
- хэширование не участвует в тестировании равенства. Верно, что равные объекты должны иметь одинаковые хэш-коды, но обратное неверно: многие значения будут иметь один и тот же хэш-код. Поэтому не пытайтесь использовать сравнение хэш-кода в качестве замены равенства. Они используют хэширование для выбора вложенной коллекции (называемой ведром в коллекциях Java мир), но они используют .equals (), чтобы фактически проверить равенство.
- вам не только не нужно беспокоиться о столкновениях, вызывающих неправильные результаты в коллекции, но и для большинства приложений, вам также *обычно* не нужно беспокоиться о производительности - хешированные коллекции Java делают довольно хорошую работу по управлению хэш-кодами.
- еще лучше, для случая, о котором вы спросили (строки как ключи), вам даже не нужно беспокоиться о хэш-кодах сами, потому что класс строки Java генерирует довольно хороший хэш-код. Так же как и большинство предоставленных классов Java.
еще немного подробностей, если хотите:
способ работы хэширования (в частности, в случае хэшированных коллекций, таких как HashMap Java, о котором вы спрашивали) заключается в следующем:
HashMap хранит значения, которые вы даете ему в коллекции подколлекций, называемых ведрами. Они фактически реализованы как связанный список. Есть ограниченное число из них: iirc, 16, чтобы начать по умолчанию, и число увеличивается по мере размещения большего количества элементов на карте. Всегда должно быть больше ведер, чем значений. Чтобы предоставить один пример, используя значения по умолчанию, если вы добавите 100 записей в хэш-карту, будет 256 ведер.
каждое значение, которое может быть использовано в качестве ключа на карте, должно иметь возможность генерировать целое значение, называемое хэш-кодом.
HashMap использует этот хэш-код для выбора корзины. В конечном счете, это означает принятие целого значения
moduloколичество ведер, но до этого у Java HashMap есть внутренний метод (называемыйhash()), который изменяет хэш-код, чтобы уменьшить некоторые известные источники слипания.при поиске значения HashMap выбирает ведро, а затем ищет отдельный элемент линейным поиском связанного списка, используя
.equals().Так: вы не нужно обходить коллизии для корректности, и вам обычно не нужно беспокоиться о них для производительности, и если вы используете собственные классы Java (например, String), вам также не нужно беспокоиться о создании значений хэш-кода.
в случае, когда вам нужно написать свой собственный метод хэш-кода (что означает, что вы написали класс с составным значением, например, пару имя/фамилия), все становится немного сложнее. Вполне возможно, чтобы получить это неправильно , но это не ракетостроение. Во-первых, знайте: единственное, что вы должны сделать, чтобы гарантировать правильность, - это гарантировать, что равные объекты дают равные хэш-коды. Поэтому, если вы пишете метод hashcode() для своего класса, вы также должны написать метод equals (), и вы должны изучить одни и те же значения в каждом.
можно написать метод hashcode (), который является плохим, но правильным, под которым я подразумеваю, что он будет удовлетворять ограничению "равные объекты должны давать равные хэш-коды", но все же очень плохо, имея много столкновений.
каноническим вырожденным худшим случаем этого было бы написать метод, который просто возвращает постоянное значение (например, 3) для всех случаев. Это означало бы, что каждое значение будет хешироваться в одно и то же ведро.
все равно работа, но производительность снизится до уровня связанного списка.
очевидно, что вы не будете писать такой ужасный метод hashcode (). Если вы используете приличный IDE, он способен генерировать один для вас. Поскольку StackOverflow любит код, вот код для класса firstname/lastname выше.
public class SimpleName { private String firstName; private String lastName; public SimpleName(String firstName, String lastName) { super(); this.firstName = firstName; this.lastName = lastName; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((firstName == null) ? 0 : firstName.hashCode()); result = prime * result + ((lastName == null) ? 0 : lastName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SimpleName other = (SimpleName) obj; if (firstName == null) { if (other.firstName != null) return false; } else if (!firstName.equals(other.firstName)) return false; if (lastName == null) { if (other.lastName != null) return false; } else if (!lastName.equals(other.lastName)) return false; return true; } }
я сильно подозреваю, что
HashMap.putметод не определяет, является ли ключ тем же самым, просто глядя наString.hashCode.там определенно будет шанс hash collision, так что можно было бы ожидать, что
String.equalsметод также будет вызван, чтобы убедиться, чтоStrings действительно равны, если действительно есть случай, когда дваStrings имеют то же значение, возвращенное изhashCode.таким образом, новый ключ
Stringбудет судить один и тот же ключStringкак тот, который уже находится вHashMapесли и только если значение, возвращенноеhashCodeравно, а тоequalsвозвращаетtrue.кроме того, чтобы добавить, эта мысль также была бы верна для классов, отличных от
String, какObjectсам класс уже имеетhashCodeиequalsметоды.Edit
Итак, чтобы ответить на вопрос, нет, было бы неплохо использовать
Stringдля клавишуHashMap.
Это не проблема, это просто, как работают хэш-таблицы. Доказуемо невозможно иметь разные хэш-коды для всех разных строк, потому что есть гораздо более разные строки, чем целые числа.
как писали другие, хэш-коллизии разрешаются с помощью метода equals (). Единственная проблема, которую это может вызвать, - это вырождение хэш-таблицы, что приводит к плохой производительности. Вот почему хэш-карта Java имеет коэффициент загрузки, соотношение между ведрами и вставленными элементами что, при превышении, приведет к повторному хэшированию стола с удвоенным количеством ведер.
обычно это работает очень хорошо, но только если хэш-функция хороша, т. е. не приводит к большему, чем статистически ожидаемое количество столкновений для вашего конкретного входного набора.
String.hashCode()хорошо в этом плане, но так было не всегда. якобы, до Java 1.2 он включал только каждый n-й символ. Это было быстрее, но вызвало предсказуемые столкновения для всех Строка, разделяющая каждый n-й символ-очень плохо, если вам не повезло иметь такой регулярный ввод, или если кто-то хочет сделать DOS-атаку на ваше приложение.
я направляю вас к ответу здесь. Пока это не плохо идея использовать строки (@CPerkins объяснил, почему, отлично), сохраняя значения в хэш-карте с целое число клавиш и лучше, так как это вообще быстрее (хотя и незаметно) и имеет меньший шанс (на самом деле, нет шансов) столкновений.
см. эту диаграмму столкновений с использованием 216553 ключей в каждом случае (украдено из этого post, переформатирован для нашего обсуждения)
Hash Lowercase Random UUID Numbers ============= ============= =========== ============== Murmur 145 ns 259 ns 92 ns 6 collis 5 collis 0 collis FNV-1a 152 ns 504 ns 86 ns 4 collis 4 collis 0 collis FNV-1 184 ns 730 ns 92 ns 1 collis 5 collis 0 collis* DBJ2a 158 ns 443 ns 91 ns 5 collis 6 collis 0 collis*** DJB2 156 ns 437 ns 93 ns 7 collis 6 collis 0 collis*** SDBM 148 ns 484 ns 90 ns 4 collis 6 collis 0 collis** CRC32 250 ns 946 ns 130 ns 2 collis 0 collis 0 collis Avg Time per key 0.8ps 2.5ps 0.44ps Collisions (%) 0.002% 0.002% 0%конечно, количество целых чисел ограничено 2^32, где как нет предела количеству строк (и нет теоретического предела количеству ключей, которые могут быть сохранены в
HashMap). Если вы используетеlong(или дажеfloat), Столкновения будут неизбежны, и поэтому не "лучше", чем строка. Однако, даже несмотря на хэш-коллизии,put()иget()всегда поместите / получите правильную пару ключ-значение (см. редактирование ниже).в конце концов, это действительно не имеет значения, так что используйте все, что удобнее. Но если удобство не имеет никакого значения, и вы не собираетесь иметь более 2^32 записей, я предлагаю вам использовать
intsкак ключи.
EDIT
хотя вышеизложенное определенно верно, никогда не используйте "StringKey".hashCode () для создания ключа вместо исходного
Stringключ для производительности- 2 разные строки могут иметь один и тот же хэш-код, вызывая перезапись на вашемput()метод. Реализация JavaHashMapдостаточно умен, чтобы обрабатывать строки (любой тип ключа, на самом деле) с тем же хэш-кодом автоматически, поэтому разумно позволить Java обрабатывать эти вещи для вас.
вы говорите о хеширования. Хэш-коллизии-это проблема независимо от типа hashcode'D. все классы, которые используют хэш-код (например, HashMap), обрабатывают хэш-коллизии просто отлично. Например, HashMap может хранить несколько объектов в ведре.
Не беспокойтесь об этом, если вы не вызывая hashCode себя. Хэш-коллизии, хотя и редкие, ничего не нарушают.
Comments