Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

HashMap

При работе с массивами я сравнивал их с коробочками. Слово HashMap содержит слово map — карта. Только это не пытайтесь найти сходство с картами в географическом атласе, с гуглокартами, с Яндекс.Картами или, на худой конец, с игральными картами. Это карточка в картотеке. Вы заполняете карточки какими-то данными и кладёте их в ящик. Если вы содержите гостиницу для котов, то скорее всего вы занесёте в карточку имя кота, возраст и т.п.

Класс HashMap использует хеш-таблицу для хранения карточки, обеспечивая быстрое время выполнения запросов get() и put() при больших наборах. Класс реализует интерфейс Map (хранение данных в виде пар ключ/значение). Ключи и значения могут быть любых типов, в том числе и null. При этом все ключи обязательно должны быть уникальны, а значения могут повторяться. Данная реализация не гарантирует порядка элементов.

Общий вид HashMap:


// K - это Key (ключ), V - Value (значение)
class HashMap<K, V>

Объявить можно следующим образом:


Map<String, Integer> hashMap = new HashMap<String, Integer>();
// или так
Map<String, String> hashMap = new HashMap<String, String>();

По умолчанию при использовании пустого конструктора создается картотека ёмкостью из 16 ячеек. При необходимости ёмкость увеличивается, вам не надо об этом задумываться.

HashMap

Вы можете указать свои ёмкость и коэффициент загрузки, используя конструкторы HashMap(capacity) и HashMap(capacity, loadFactor). Максимальная ёмкость, которую вы сможете установить, равна половине максимального значения int (1073741824).

Добавление элементов происходит при помощи метода put(K key, V value). Вам надо указать ключ и его значение.


hashMap.put("0", "Васька");

Узнаем размер:


hashMap.size();

Проверяем ключ и значение на наличие:


hashMap.containsKey("0");
hashMap.containsValue("Васька");

Выбираем все ключи:


for (String key : hashMap.keySet()) {
    System.out.println("Key: " + key);
}

Выбираем все значения:


for (int value : hashMap.values()) {
    System.out.println("Value: " + value);
}

Выбираем все ключи и значения одновременно:


for (Map.Entry entry : hashMap.entrySet()) {
    System.out.println("Key: " + entry.getKey() + " Value: "
        + entry.getValue());
}

Пример первый


// Создадим хеш-карточку
Map<String, Integer> hashMap = new HashMap<>();

// Помещаем данные на карточку
hashMap.put("Васька", 5);
hashMap.put("Мурзик", 8);
hashMap.put("Рыжик", 12);
hashMap.put("Барсик", 5);

// Получаем набор элементов
Set<Map.Entry<String, Integer>> set = hashMap.entrySet();

// Отобразим набор
for (Map.Entry<String, Integer> me : set) {
    System.out.print(me.getKey() + ": ");
    System.out.println(me.getValue());
}

// Добавляем значение
int value = hashMap.get("Рыжик");
hashMap.put("Рыжик", value + 3);
System.out.println("У Рыжика стало " + hashMap.get("Рыжик"));

Если вы посмотрите на результат, то увидите, что данные находятся не в том порядке, в котором вы заносили. Второй важный момент - если в карточке уже существует какой-то ключ, то если вы помещаете в него новое значение, то ключ перезаписывается, а не заносится новый ключ.

В древних версиях Java приходилось добавлять новые значения следующим образом.


hashMap.put("Мурзик", new Integer(8));
// или
hashMap.put("Мурзик", Integer.valueOf(8));

Потом Java поумнела и стала самостоятельно переводить число типа int в объект Integer. Но это не решило основной проблемы - использование объектов очень сильно сказывается на потреблении памяти. Поэтому в Android были предложены аналоги этого класса (см. ниже). Ключом в Map может быть любой объект, у которого корректно реализованы методы hashCode() и equals().

Пример второй

Так как ключи являются уникальными, мы можем написать следующую программу - сгенерируем набор случайных чисел сто раз и посчитаем количество повторов. Map легко решит эту задачу - в качестве ключа используется сгенерированное число, а в качестве значения - количество повторов.


Random random = new Random(36);
Map<Integer, Integer> hashMap = new HashMap<>();

for (int i = 0; i < 100; i++){
    // Создадим число от 0 до 10
    int number = random.nextInt(10);
    Integer frequency = hashMap.get(number);
    hashMap.put(number, frequency == null ? 1 : frequency + 1);
}
System.out.println(hashMap);

Метод get() возвращает null, если ключ отсутствует, т.е число было сгенерировано впервые или в противном случае метод возвращает для данного ключа ассоциированное значение, которое увеличивается на единицу.

Пример третий

Пример для закрепления материала. Поработаем с объектами классов. Нужно самостоятельно создать класс Pet и его наследников Cat, Dog, Parrot.

Создадим отображение из домашних животных, где в качестве ключа выступает строка, а в качестве значения класс Pet.


Map<String, Pet> hashMap = new HashMap<>();

hashMap.put("Кот", new Cat("Мурзик"));
hashMap.put("Собака", new Dog("Бобик"));
hashMap.put("Попугай", new Parrot("Кеша"));
System.out.println(hashMap);
Pet cat = hashMap.get("Кот");
System.out.println(cat);
System.out.println(hashMap.containsKey("Кот"));
System.out.println(hashMap.containsValue(cat));

Многомерные отображения

Контейнеры Map могут расширяться до нескольких измерений, достаточно создать контейнер Map, значениями которого являются контейнеры Map (значениями которых могут быть другие контейнеры). Предположим, вы хотите хранить информацию о владельцах домашних животных, у каждого из которых может быть несколько любимцев. Для этого нам нужно создать контейнер Map<Person, List<Pet>>.


Map<Person, List<? extends Pet>> personMap = new HashMap<>();

personMap.put(new Person("Иван"), Arrays.asList(new Cat("Барсик"), new Cat("Мурзик")));
personMap.put(new Person("Маша"), Arrays.asList(new Cat("Васька"), new Dog("Бобик")));
personMap.put(new Person("Ирина"), Arrays.asList(new Cat("Рыжик"), new Dog("Шарик"), new Parrot("Гоша")));

System.out.println("personMap: " + personMap);
System.out.println("personMap.keySet(): " + personMap.keySet());

for(Person person : personMap.keySet()){
    System.out.println(person + " имеет");
    for (Pet pet : personMap.get(person)){
        System.out.println("  " + pet);
    }
}

Метод keySet() возвращает контейнер Set, содержащий все ключи из personMap, который используется в цикле для перебора элементов Map.

Sparse arrays - аналог в Android

Разработчик Android посчитали, что HashMap не слишком оптимизирован для мобильных устройств и предложили свой вариант в виде специальных массивов. Данные классы являются родными для Android, но не являются частью Java. Очень рекомендуют использовать именно Android-классы. Не все программисты знают об этих аналогах, а также классический код может встретиться в различных Java-библиотеках. Если вы увидите такой код, то заменить его на нужный. Ниже представлена таблица для замены.

HashMapArray class
HashMap<K,V>ArrayMap<K,V>
HashMap<Integer, Object>SparseArray<Object>
HashMap<Integer, Boolean>SparseBooleanArray
HashMap<Integer, Integer>SparseIntArray
HashMap<Integer, Long>SparseLongArray
HashMap<Long, Object>LongSparseArray<Object>

Подробнее о Sparse arrays

Существует ещё класс HashTable, который очень похож в использовании как и HashMap.

Дополнительное чтение

Список с использованием HashMap и объектов (SimpleAdapter)

Структуры данных в картинках. HashMap

Реклама