Я пытаюсь rehash() мой HashTable каждый раз, когда я получаю столкновение, но я продолжаю получать ошибку кучи Java.
В принципе, у меня есть таблица String [], длина которой я хочу умножить на 2 при каждом столкновении в моем хеше.
Изменение: я использую insert() в цикле while, который загружает около 300 000 слов в хеш-таблицу.
public void rehash() {
String[] backup = table;
size = size * 2;
// i get the error on the line below
table = new String[size];
System.out.println("size" + size);
for (int i = 0; i < backup.length; i++) {
if (backup[i] != null) {
insert(backup[i]);
}
}
public void insert(String str) {
int index = hashFunction(str);
if (index > size || table[index] != null) {
rehash();
}
table[index] = str;
}
Моя хэш-функция:
int val= 0;
val= s.hashCode();
if (val< 0) {
val*= -1;
}
while (val> this.size) {
val%= this.size;
}
return val;
public void load() {
String str = null;
try {
BufferedReader in = new BufferedReader(new FileReader(location));
while ((str = in.readLine()) != null) {
insert(str);
}
in.close();
} catch (Exception e) {
System.out.println("exception");
}
}
Независимо от того, насколько большой вы делаете таблицу, вы не можете полностью избежать столкновений. Попробуйте эту программу, например:
System.out.println("Aaa".hashCode());
System.out.println("AbB".hashCode());
System.out.println("BBa".hashCode());
System.out.println("BCB".hashCode());
Выход:
65569
65569
65569
65569
Это четыре разные строки с точно таким же хэш-кодом. Точных столкновений такого рода не так уж и редко. (Хэш-алгоритм, используемый классом Java String, на самом деле не очень хороший, но он поддерживается для обратной совместимости.)
Таким образом, создание хэш-таблицы больше (с использованием большей части хэш-кода) уменьшает количество столкновений, но никогда не будет полностью их предотвращать, потому что иногда хэш-коды для разных значений точно такие же.
Хэш-таблица должна быть подготовлена для решения ограниченного числа столкновений, имея возможность хранить набор разных значений в одном слоте таблицы. Обычно это делается с помощью связанного списка для значений, которые используют один и тот же хэш-код. Текущая реализация java.util.HashMap
делает что-то более продвинутое: если значения с одним и тем же хэш-кодом реализуют интерфейс Comparable
(как это делает String
), он использует это для организации их в двоичном дереве. Существует также что-то возможное, называемое динамическим совершенным хешированием, где столкновениям препятствует динамическое изменение алгоритма хеширования, чтобы гарантировать, что каждое отдельное значение получает четкий хеш, но это сложнее.
Несколько других проблем, которые я вижу в вашем коде:
Нет необходимости инициализировать val с помощью 0, если вы сразу же присвоите ему что-то еще на следующей строке. Вместо этого вы можете сделать int val; val = s.hashCode();
int val; val = s.hashCode();
или просто int val = s.hashCode();
,
Проверка: if (val < 0) val *= -1;
не является полностью надежным, потому что если val точно равно Integer.MIN_VALUE
, умножая его на -1 переполнение и в результате производит Integer.MIN_VALUE
. Чтобы полностью исключить отрицательные значения, замаскируйте бит знака целого числа, выполнив val &= Integer.MAX_VALUE;
,
Условие здесь неверно: while (val > this.size) val %= this.size;
, Он должен быть val >= this.size
. Тем не менее, нет необходимости цитировать вообще. Выполнение операции по модулю один раз безоговорочно без каких-либо пока/если этого достаточно. Альтернативно, если вы поддерживаете размер таблицы как точную мощность 2, вы можете реализовать операцию мод как: val &= (size - 1);
, что немного быстрее и будет также выполнять требование обеспечения того, чтобы результат был неотрицательным, в отличие от %
.
В методе insert это должно быть, if (index >= size...
, а не if (index > size...
, но на самом деле нет необходимости в этой проверке вообще, если хэш-функция уже обеспечивает хэш в диапазоне.
Когда слот таблицы уже занят, вам нужно проверить, содержит ли он уже ту же строку, которую вы пытаетесь вставить (в этом случае вы можете немедленно вернуться из метода), а не просто принимать другое значение при столкновении.
Из хеш-функции, которую вы опубликовали, не ясно, что она возвращает, но похоже, что у нее есть проблема.
int index = hashFunction(str);
здесь, если ваш индекс не является правильным, чем ваш код делает много рекурсивных новых String [size]. Запустите здесь счетчик или отладочную точку и проверьте.
if (index > size || table[index] != null) {
rehash();
}
Из javadoc
Как правило, коэффициент загрузки по умолчанию (.75) предлагает хороший компромисс между затратами времени и пространства. Более высокие значения уменьшают объем служебных данных, но увеличивают стоимость поиска (отражается в большинстве операций класса HashMap, включая get и put). Ожидаемое количество записей на карте и коэффициент загрузки должны учитываться при настройке начальной емкости, чтобы минимизировать количество операций перефразирования. Если начальная емкость больше максимального количества записей, деленная на коэффициент нагрузки, никаких операций перефразирования никогда не произойдет.
Если вы знаете, что карта будет использоваться для хранения N записей aprox, хорошая начальная емкость будет N/.75 + N/10 - с учетом дисперсии 10%.
Этот метод (resize) вызывается автоматически, когда количество ключей на этой карте достигает своего порога
где threshold = (int)(capacity * loadFactor);