Сравнение HashSet и TreeSet

1. Вступление

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

2. Различия

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

2.1. Заказ

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

@Test
public void givenTreeSet__whenRetrievesObjects__thenNaturalOrder() {
    Set<String> set = new TreeSet<>();
    set.add("Baeldung");
    set.add("is");
    set.add("Awesome");

    assertEquals(3, set.size());
    assertTrue(set.iterator().next().equals("Awesome"));
}

После добавления объектов String в TreeSet мы видим, что первый - «Awesome», хотя он был добавлен в самом конце. Подобная операция, выполненная с HashSet , не гарантирует, что порядок элементов будет оставаться постоянным во времени.

2.2. Null Objects

Другое отличие состоит в том, что HashSet может хранить null объекты, тогда как TreeSet не позволяет им :

@Test(expected = NullPointerException.class)
public void givenTreeSet__whenAddNullObject__thenNullPointer() {
    Set<String> set = new TreeSet<>();
    set.add("Baeldung");
    set.add("is");
    set.add(null);
}

@Test
public void givenHashSet__whenAddNullObject__thenOK() {
    Set<String> set = new HashSet<>();
    set.add("Baeldung");
    set.add("is");
    set.add(null);

    assertEquals(3, set.size());
}

Если мы попытаемся сохранить объект null в TreeSet , операция приведет к выбросу NullPointerException . Единственное исключение было в Java 7, когда было разрешено иметь ровно один элемент null в TreeSet .

2.3. Спектакль

  • Проще говоря, HashSet быстрее, чем TreeSet . **

HashSet обеспечивает постоянную производительность для большинства операций, таких как add () , remove () и contains () , по сравнению с временем log ( n ), предлагаемым TreeSet.

Обычно мы видим, что время выполнения для добавления элементов в TreeSet намного лучше, чем для HashSet .

Пожалуйста, помните, что JVM может не разогреваться, поэтому время выполнения может отличаться. Хорошее обсуждение того, как проектировать и выполнять микротесты с использованием различных реализаций Set , доступно по адресу here .

2.4. Реализованные методы

  • TreeSet богат функциональными возможностями ** , реализуя дополнительные методы, такие как:

  • pollFirst () - вернуть первый элемент или null , если Set равен

пустой pollLast () ** - получить и удалить последний элемент или вернуть

null , если Set пусто first () ** - вернуть первый элемент

  • last () - чтобы вернуть последний элемент

  • ceiling () - вернуть наименьший элемент, больший или равный

данный элемент или null , если такого элемента нет lower () ** - вернуть самый большой элемент строго меньше, чем

заданный элемент или null , если такого элемента нет

Упомянутые выше методы делают TreeSet намного проще в использовании и более мощным, чем HashSet .

3. сходства

3.1. Уникальные элементы

И TreeSet , и HashSet гарантируют коллекцию элементов без дубликатов, поскольку она является частью универсального интерфейса Set :

@Test
public void givenHashSetAndTreeSet__whenAddDuplicates__thenOnlyUnique() {
    Set<String> set = new HashSet<>();
    set.add("Baeldung");
    set.add("Baeldung");

    assertTrue(set.size() == 1);

    Set<String> set2 = new TreeSet<>();
    set2.add("Baeldung");
    set2.add("Baeldung");

    assertTrue(set2.size() == 1);
}

3.2. Не синхронизировано

  • Ни одна из описанных реализаций Set не синхронизирована . ** Это означает, что если несколько потоков обращаются к Set одновременно, и хотя бы один из потоков модифицирует его, то он должен быть синхронизирован извне.

3.3. Отказоустойчивые итераторы

  • _Iterator , возвращаемый TreeSet и HashSet_ , не подвержен сбоям. **

Это означает, что любая модификация Set в любое время после создания Iterator вызовет исключение ConcurrentModificationException:

@Test(expected = ConcurrentModificationException.class)
public void givenHashSet__whenModifyWhenIterator__thenFailFast() {
    Set<String> set = new HashSet<>();
    set.add("Baeldung");
    Iterator<String> it = set.iterator();

    while (it.hasNext()) {
        set.add("Awesome");
        it.next();
    }
}

4. Какую реализацию использовать?

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

Вот несколько быстрых моментов, которые нужно запомнить:

  • Если мы хотим сохранить наши записи отсортированными, нам нужно перейти к TreeSet

  • Если мы оцениваем производительность больше, чем потребление памяти, мы должны

HashSet ** Если у нас мало памяти, мы должны пойти на TreeSet

  • Если мы хотим получить доступ к элементам, которые находятся относительно близко друг к другу

в соответствии с их естественным порядком, мы могли бы рассмотреть TreeSet потому что он имеет большую местность Производительность ** HashSet ‘ можно настроить с помощью initialCapacity__ и

loadFactor , что невозможно для TreeSet ** Если мы хотим сохранить порядок ввода и извлечь выгоду из постоянного времени

доступ, мы можем использовать LinkedHashSet

5. Заключение

В этой статье мы рассмотрели различия и сходства между TreeSet и HashSet .

Как всегда, примеры кода для этой статьи доступны over на GitHub .