Рубрики
Без рубрики

Сравнение хэш-наборов и наборов деревьев

Краткий обзор сходств и различий между HashSet и TreeSet в Java.

Автор оригинала: baeldung.

1. введение

В этой статье мы сравним две наиболее популярные реализации Java java.util.Установить интерфейс – хэш-набор и набор деревьев .

2. Различия

HashSet и TreeSet являются листьями одной и той же ветви, но они отличаются несколькими важными моментами.

2.1. Заказ

HashSet хранит объекты в случайном порядке, тогда как TreeSet применяет естественный порядок элементов. Давайте рассмотрим следующий пример:

@Test
public void givenTreeSet_whenRetrievesObjects_thenNaturalOrder() {
    Set set = 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() {
    Set set = 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() {
    Set set = 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() {
    Set set = 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 .