Проблемы с моей реализацией Java Hashmap
После некоторого изучения и поиска старых сообщений я понимаю, что когда вы используете Hashmap или Hashtable в Java, где Strings - это ключи, первый "раунд" хэширования применяется к hashCode каждого объекта String (очевидно, существует второй хэш функция, которая применяется к результату int hashCode()
), где по умолчанию int hashCode()
имеет некоторое отношение к расположению вызывающих в памяти (из того, что я прочитал). С учетом вышесказанного, если бы у меня была карта с определенным разработчиком классом для ключей, я прочитал, что могу переопределить int hashCode()
и использовать несколько различных полей моего объекта, чтобы получить максимально уникальное значение int для каждого объекта. Однако рассмотрим фрагмент кода ниже, который содержит массивы примитивных типов.
import java.util.HashMap;
public class test
{
public static void main(String[] args) {
HashMap<char[], int[] > map = new HashMap<char[], int[]>();
String s = "Hello, World";
int x[] = { 1, 2, 3, 4, 5 };
map.put( s.toCharArray(), x );
x = map.get( s );
for ( int i : x )
System.out.print( i );
}
}
Программа вылетает из NullPointerException
конечно потому что map.get( s );
возвращает ноль. Я подозреваю, что это произошло, потому что есть две разные ссылки между map.put()
а также map.get()
, То, что я хочу, чтобы программа вывела: 1 2 3 4 5.
Мой вопрос: Как я могу получить приведенный выше фрагмент кода для поиска ключей по значению ключа по сравнению с ссылкой на ключ? То есть как мне заставить программу выводить 1 2 3 4 5?
Изменить: я использую хэш-карту в качестве справочной таблицы. Я читаю строки из файла и мне нужен быстрый способ определить, находится ли строка, которую я только что прочитал, в таблице или нет.
4 ответа
- "Где-то читал, что есть 2 раунда хеширования". Нет. Если вы хотите посмотреть, как хэшируются строки - посмотрите на код.
- Основной контракт хэш-карты заключается в том, что она будет извлекать элементы, если ключи имеют одинаковый хэш и равны по своим
equals
функция. Почему вы так думаетеchar[]
имеет переопределенныйequals
что позволяет ему сравнивать себя со строками правильно? Или даже между собой? Он не переопределяет equals и вернет true, только если это тот же экземпляр. - Вы используете массивы для ключей. Это возможно, но большинство старших разработчиков будут кричать на вас за это. Нет хорошего способа сравнить их: Object.equals (который они используют по умолчанию) означает, что вы не можете воспроизвести массив - вы должны использовать точно такой же объект. Использование Arrays.equals (или аналогичных средств для сравнения содержимого) будет означать, что в качестве ключей у вас есть изменяемые объекты - ПЛОХО.
Что касается вашего комментария, я понимаю, что вы хотите иметь возможность редактировать строку. Это хорошо, прежде чем положить его на карту. Но не изменяйте объекты, когда они являются ключами на карте, таким образом, чтобы это могло изменить хэш-код или равнозначно. Лучший способ сделать это - использовать пользовательский объект. Массивы не подходят для этого.
Если вы действительно хотите изменить значения на лету, пока они на карте - используйте BiMap
(или делать то, что он делает в forcePut
сам).
Просто используйте строку в качестве ключа для карты.
HashMap<String, int[] > map = new HashMap<String, int[]>();
String key = "array1";
int x[] = { 1, 2, 3, 4, 5 };
map.put( key, x );
Строка является неизменной, поэтому она является хорошим выбором в качестве ключа для карты.
Добавление другого массива:
String key2 = "array2";
int x2[] = { 6, 7, 8, 9, 10 };
map.put( key2, x2 );
Вывод значений:
x = map.get( key );
for ( int i : x )
System.out.print( i + " " );
}
дает
1 2 3 4 5
x = map.get( key2 );
for ( int i : x )
System.out.print( i + " " );
}
дает
6 7 8 9 10
Есть много способов сделать это. Одно из самых маленьких изменений, которые вы можете сделать, - это просто сохранить char[]
перед звонком put()
и использовать тот же аргумент get()
:
char[] charArray = s.toCharArray();
map.put(charArray, x);
x = map.get(charArray);
Здесь важно то, что вам нужно использовать один и тот же объект для get()
как вы привыкли put()
,
Из документации Oracle
public V get(Object key)
Returns the value to which the specified key is mapped, or null if this
map contains no mapping for the key.
More formally, if this map contains a mapping from a key k to a value v
such that (key==null ? k==null : key.equals(k)), then this method returns v;
otherwise it returns null.
и, очевидно, s.equals(s.toCharArray()) имеет значение false. Они даже не того же класса.
Тем не менее, вы можете использовать в качестве ключа класс, который переопределяет equals() таким образом, что возвращает true для вашего случая. Например:
class MyCharArray {
private char[] data;
@Override
public boolean equals(Object o) {
if (o instanceOf String) {
return data.equals(o.toCharArray);
else {
return false;
}
}
...
}