1. введение
В этой статье мы сравним две наиболее популярные реализации Java java.util.Установить интерфейс – хэш-набор и набор деревьев .
2. Различия
HashSet и TreeSet являются листьями одной и той же ветви, но они отличаются несколькими важными моментами.
2.1. Заказ
HashSet хранит объекты в случайном порядке, тогда как TreeSet применяет естественный порядок элементов. Давайте рассмотрим следующий пример:
@Test public void givenTreeSet_whenRetrievesObjects_thenNaturalOrder() { Setset = new TreeSet<>(); set.add("Baeldung"); set.add("is"); set.add("Awesome"); assertEquals(3, set.size()); assertTrue(set.iterator().next().equals("Awesome")); }
После добавления объектов String в TreeSet мы видим , что первый из них “Потрясающий”, хотя он был добавлен в самом конце. Аналогичная операция, выполняемая с HashSet , не гарантирует, что порядок элементов останется постоянным с течением времени.
2.2. Нулевые объекты
Другое отличие заключается в том, что HashSet может хранить null объекты, в то время как TreeSet не разрешает их :
@Test(expected = NullPointerException.class) public void givenTreeSet_whenAddNullObject_thenNullPointer() { Setset = new TreeSet<>(); set.add("Baeldung"); set.add("is"); set.add(null); } @Test public void givenHashSet_whenAddNullObject_thenOK() { Set set = new HashSet<>(); set.add("Baeldung"); set.add("is"); set.add(null); assertEquals(3, set.size()); }
Если мы попытаемся сохранить объект null в наборе деревьев , операция приведет к исключению NullPointerException . Единственное исключение было в Java 7, когда было разрешено иметь ровно один null элемент в наборе деревьев .
2.3. Производительность
Проще говоря, Хэш-набор быстрее, чем Набор деревьев .
HashSet обеспечивает постоянную производительность для большинства операций, таких как add () , remove() и contains () , по сравнению со временем log ( n ), предлагаемым набором деревьев .
Обычно мы видим, что время выполнения для добавления элементов в TreeSet намного лучше, чем для HashSet .
Пожалуйста, помните, что JVM может быть не разогрет, поэтому время выполнения может отличаться. Хорошее обсуждение того, как проектировать и выполнять микротесты с использованием различных реализаций Set , доступно здесь .
2.4. Реализованные методы
Набор деревьев богат функциональными возможностями , реализующими дополнительные методы, такие как:
- pollFirst() – для возврата первого элемента или null , если Set пуст
- pollLast() – для извлечения и удаления последнего элемента или возврата null , если Set пуст
- first() – для возврата первого элемента
- last() – для возврата последнего элемента
- ceiling() – для возврата наименьшего элемента, большего или равного данному элементу, или null , если такого элемента нет
- lower() – для возврата самого большого элемента, строго меньшего, чем данный элемент, или null , если такого элемента нет
Методы, упомянутые выше, делают TreeSet намного проще в использовании и мощнее, чем HashSet .
3. Сходство
3.1. Уникальные элементы
Оба TreeSet и HashSet гарантируют коллекцию элементов без дубликатов, поскольку она является частью общего набора интерфейса:
@Test public void givenHashSetAndTreeSet_whenAddDuplicates_thenOnlyUnique() { Setset = new HashSet<>(); set.add("Baeldung"); set.add("Baeldung"); assertTrue(set.size() == 1); Set set2 = new TreeSet<>(); set2.add("Baeldung"); set2.add("Baeldung"); assertTrue(set2.size() == 1); }
3.2. Не синхронизировано
Ни одна из описанных реализаций Set не является синхронизированной . Это означает, что если несколько потоков одновременно обращаются к набору и хотя бы один из потоков изменяет его, то он должен быть синхронизирован извне.
3.3. Отказоустойчивые итераторы
То Итератор s возвращается Набор деревьев и Хэш-набор быстро терпят неудачу.
Это означает, что любая модификация Set в любое время после создания Итератора вызовет исключение ConcurrentModificationException:
@Test(expected = ConcurrentModificationException.class) public void givenHashSet_whenModifyWhenIterator_thenFailFast() { Setset = new HashSet<>(); set.add("Baeldung"); Iterator it = set.iterator(); while (it.hasNext()) { set.add("Awesome"); it.next(); } }
4. Какую реализацию использовать?
Обе реализации выполняют контракт идеи набора, так что это зависит от контекста, какую реализацию мы могли бы использовать.
Вот несколько быстрых моментов, которые нужно запомнить:
- Если мы хотим, чтобы наши записи были отсортированы, нам нужно перейти к набору деревьев
- Если мы ценим производительность больше, чем потребление памяти, мы должны использовать HashSet
- Если у нас не хватает памяти, мы должны перейти к набору TreeSet
- Если мы хотим получить доступ к элементам, которые относительно близки друг к другу в соответствии с их естественным порядком, мы можем рассмотреть Набор деревьев , потому что он имеет большую локальность
- Производительность HashSet можно настроить с помощью начальной емкости и loadFactor , что невозможно для набора деревьев
- Если мы хотим сохранить порядок вставки и извлечь выгоду из постоянного доступа к времени, мы можем использовать LinkedHashSet
5. Заключение
В этой статье мы рассмотрели различия и сходства между TreeSet и HashSet .
Как всегда, примеры кода для этой статьи доступны на GitHub .