Интерфейс Set и классы HashSet, LinkedHashSet
1. Интерфейс Set
Интерфейс Set определяет множество (набор).
Он расширяет Collection и определяет поведение коллекций, не допускающих дублирования элементов. Таким образом, метод add()
возвращает false
, если делается попытка добавить дублированный элемент в набор.
Интерфейс не определяет никаких собственных дополнительных методов.
Интерфейс Set
заботится об уникальности хранимых объектов, уникальность определятся реализацией метода equals()
. Поэтому если объекты создаваемого класса будут добавляться в Set
, желательно переопределить метод equals()
.
Ниже представлена ветка иерархии интерфейса Set:
2. Класс HashSet
HashSet
реализует интерфейс Set
и создает коллекцию, которая хранит элементы в хеш-таблице.Хеш-таблица
Вычисление хеш-кода
Рассмотрим пример вычисления хеш-кодов:
Ключ | Алгоритм хеш-кода | Хеш-код |
---|---|---|
Alex | A(1) + l(12) + e(5) + x(24) | 42 |
Bob | B(2) + o(15) + b(2) | 19 |
Dirk | D(4) + i(9) + r(18) + k(11) | 42 |
Fred | F(6) + r(18) + e(5) + d(4) | 33 |
Допустим, чтобы получить хеш-код, я сопоставляю каждый символ из ключа с номером этого символа в алфавите и получаю их сумму. Таким образом "Bob" попадает в бакет 19, "Fred" в бакет 33. Как видно из примера, для ключей "Alex" и "Dirk" получается одинаковый хеш-код, что тоже допустимо. Такая ситуация называется коллизией, два значения попадают в один бакет с номером 42.
Каким образом происходит обратный процесс - поиск элемента в хеш-таблице? У нас есть ключ объекта, по которому мы вычисляем хеш-код и определяем бакет, в котором находится наш объект. Но что делать если в бакете находится не один, а несколько элементов? Конечно, же это не значит, что хеш-таблица вернет любой объект из бакета. Если объектов в бакете несколько, далее с помощью метода equals()
определяется нужный объект.
Контракт между методами hashCode() и equals()
Как Вы уже поняли, методы hashCode()
и equals()
тесно связаны между собой. Поэтому существуют специальный контракт написания методов hashCode()
и equals()
:
- Для одного и того же объекта, хеш-код всегда будет одинаковым.
- Если объекты одинаковые, то и хеш-коды одинаковые (но не наоборот).
- Если хеш-коды равны, то входные объекты не всегда равны.
- Если хеш-коды разные, то и объекты гарантированно будут разные.
На самом деле, корректным, но не эффективным может быть такое определение хеш-кода:
@Override
public int hashCode() {
return 1;
}
Это определение метода hashCode()
соответствует контракту, но он разместит все объекты класса в один бакет, что нивелирует все достоинства хеш-таблицы.
Создание метода hashCode()
hashCode()
. Обычно все пользуются этим вариантом создания метода.Конструкторы класса HashSet
Давайте рассмотрим конструкторы класса HashSet:
HashSet()
- начальная емкость по умолчанию (initialCapacity) – 16, коэффициент загрузки по умолчанию (loadFactor) – 0,75.HashSet(int initialCapacity)
- коэффициент загрузки – 0,75.HashSet(int initialCapacity, float loadFactor)
HashSet(Collection collection)
– конструктор, добавляющий элементы из другой коллекции.
В конструкторах можно указывать такие параметры как начальная емкость и коэффициент загрузки. Давайте разберемся что это и для чего они нужны.
Начальная емкость (initial capacity) – это изначальное количество ячеек (buckets) в хэш-таблице. Если все ячейки будут заполнены, их количество увеличится автоматически.
Коэффициент загрузки (load factor) – это показатель того, насколько заполненным может быть HashSet
до того момента, когда его емкость автоматически увеличится. Когда количество элементов в HashSet
становится больше, чем capacity * loadfactor
, хэш-таблица ре-хэшируется (заново вычисляются хэшкоды элементов, и таблица перестраивается согласно полученным значениям) и количество buckets в ней увеличивается в два раза.
Коэффициент загрузки, равный 0,75, в среднем обеспечивает хорошую производительность. Если этот параметр увеличить, тогда уменьшится нагрузка на память (так как это уменьшит количество операций ре-хэширования и перестраивания), но это повлияет на операции добавления и поиска. Чтобы минимизировать время, затрачиваемое на ре-хэширование, нужно правильно подобрать параметр начальной емкости.
Достоинства и недостатки класса HashSet
add()
, contains()
, remove()
и size()
, даже для больших наборов. Сложность выполнения операций будет О(1). В худшем случае (если в хэш-таблице только один bucket) сложность выполнения будет O(n) для Java 7 и О(log n) для Java 8.HashSet
(или можно даже сказать особенность) в том, что он не гарантирует упорядоченности элементов, поскольку процесс хеширования сам по себе обычно не приводит к созданию отсортированных множеств.Пример использования класса HashSet
Рассмотрим пример использования класса HashSet
:
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Харьков");
set.add("Киев");
set.add("Львов");
set.add("Кременчуг");
set.add("Харьков");
System.out.println(set);
}
}
3. Класс LinkedHashSet
Класс LinkedHashSet
языка Java расширяет HashSet
, не добавляя никаких новых методов.
LinkedHashSet
поддерживает связный список элементов набора в том порядке, в котором они вставлялись. Это позволяет организовать упорядоченную итерацию вставки в набор. Но это приводит к тому что класс LinkedHashSet
выполняет операции дольше чем класс HashSet
.
Рассмотрим пример использования класса LinkedHashSet
:
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetExample {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();
set.add("Бета");
set.add("Aльфa");
set.add("Этa");
set.add("Гaммa");
set.add("Эпсилон");
set.add("Oмeгa");
System.out.println(set);
}
}
Курс 'Java для начинающих' на Udemy
Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.