Hashset vs Treeset

Я всегда любил деревья, такие красивые O(n*lg(n)) и аккуратность их. Однако каждый инженер-программист, которого я когда-либо знал, спросил меня, почему я бы использовал TreeSet . Из CS-фона я не думаю, что это имеет значение для всего, что вы используете, и мне не нужно возиться с hash-функциями и ведрами (в случае Java ).

В каких случаях я должен использовать HashSet над TreeSet ?

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

    HashSet

    • class предлагает постоянную производительность времени для основных операций (добавление, удаление, наличие и размер).
    • это не гарантирует, что порядок элементов будет оставаться постоянным с течением времени
    • производительность итерации зависит от начальной емкости и коэффициента загрузки HashSet.
      • Весьма безопасно принимать коэффициент загрузки по умолчанию, но вы можете указать начальную емкость, которая примерно в два раза превышает размер, на который вы ожидаете, что набор будет расти.

    TreeSet

    • гарантирует log (n) временную стоимость для основных операций (добавление, удаление и содержит)
    • гарантирует, что элементы набора будут отсортированы (восходящие, естественные или заданные вами через его конструктор) (реализует SortedSet )
    • не предлагает никаких параметров настройки для производительности итерации
    • предлагает несколько удобных методов для работы с упорядоченными наборами, такими как first() , last() , headSet() и tailSet() т. д.

    Важные моменты:

    • Оба гарантируют дублирование коллекции элементов
    • Как правило, быстрее добавлять элементы в HashSet, а затем преобразовывать коллекцию в TreeSet для повторного сортированного обхода без дубликатов.
    • Ни одна из этих реализаций не синхронизирована. То есть, если несколько streamов обращаются к набору одновременно, и по крайней мере один из streamов изменяет набор, он должен быть синхронизирован извне.
    • LinkedHashSet в некотором смысле является промежуточным между HashSet и TreeSet . Тем не менее, реализованная как хеш-таблица со связанным списком, проходящим через нее, она предоставляет итерацию с упорядочением вставки, которая не такая же, как отсортированный обход, гарантированный TreeSet .

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

    • например SortedSet s = new TreeSet(hashSet);

    Одно из преимуществ, о которых еще не упоминалось в TreeSet заключается в том, что у него больше «локальность», что является сокращением для выражения (1), если две записи находятся рядом в порядке, TreeSet помещает их рядом друг с другом в структуру данных и, следовательно, в память ; и (2) это место размещения использует принцип локальности, в котором говорится, что подобные данные часто обращаются к приложению с аналогичной частотой.

    Это контрастирует с HashSet , который распространяет записи по всей памяти независимо от их ключей.

    Когда затраты на считывание с жесткого диска в тысячи раз превышают затраты на чтение из кеша или ОЗУ, а когда данные действительно доступны с локальностью, TreeSet может быть намного лучшим выбором.

    HashSet – это O (1) для доступа к элементам, поэтому это, безусловно, имеет значение. Но поддерживать порядок объектов в наборе невозможно.

    TreeSet полезен, если для вас важно поддерживать порядок (в терминах значений, а не порядка вставки). Но, как вы уже отмечали, вы торгуете ордером для более медленного времени доступа к элементу: O (log n) для основных операций.

    Из javadocs для TreeSet :

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

    1.HashSet разрешает нулевой объект.

    2.TreeSet не разрешает нулевой объект. Если вы попытаетесь добавить нулевое значение, это вызовет исключение NullPointerException.

    3.HashSet намного быстрее, чем TreeSet.

    например

      TreeSet ts = new TreeSet(); ts.add(null); // throws NullPointerException HashSet hs = new HashSet(); hs.add(null); // runs fine 

    Причина, по которой большинство из них использует HashSet заключается в том, что операции (в среднем) O (1) вместо O (log n). Если набор содержит стандартные элементы, вы не будете «возиться с hash-функциями», как это было сделано для вас. Если набор содержит пользовательские classы, вы должны реализовать hashCode для использования HashSet (хотя эффективная Java показывает, как), но если вы используете TreeSet вам нужно сделать это Comparable или предоставить Comparator . Это может быть проблемой, если class не имеет определенного порядка.

    Я иногда использовал TreeSet (или фактически TreeMap ) для очень маленьких наборов / карт (<10 элементов), хотя я не проверял, есть ли реальная прибыль при этом. Для больших множеств разница может быть значительной.

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

    Основываясь на прекрасном визуальном ответе на Картах на @shevchyk, вот мой прием:

     ╔══════════════╦═════════════════════╦═══════════════════╦═════════════════════╗ ║ Property ║ HashSet ║ TreeSet ║ LinkedHashSet ║ ╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣ ║ ║ no guarantee order ║ sorted according ║ ║ ║ Order ║ will remain constant║ to the natural ║ insertion-order ║ ║ ║ over time ║ ordering ║ ║ ╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣ ║ Add/remove ║ O(1) ║ O(log(n)) ║ O(1) ║ ╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣ ║ ║ ║ NavigableSet ║ ║ ║ Interfaces ║ Set ║ Set ║ Set ║ ║ ║ ║ SortedSet ║ ║ ╠══════════════╬═════════════════════╬═══════════════════╬═════════════════════╣ ║ ║ ║ not allowed ║ ║ ║ Null values ║ allowed ║ 1st element only ║ allowed ║ ║ ║ ║ in Java 7 ║ ║ ╠══════════════╬═════════════════════╩═══════════════════╩═════════════════════╣ ║ ║ Fail-fast behavior of an iterator cannot be guaranteed ║ ║ Fail-fast ║ impossible to make any hard guarantees in the presence of ║ ║ behavior ║ unsynchronized concurrent modification ║ ╠══════════════╬═══════════════════════════════════════════════════════════════╣ ║ Is ║ ║ ║ synchronized ║ implementation is not synchronized ║ ╚══════════════╩═══════════════════════════════════════════════════════════════╝ 

    Если вы не вставляете достаточное количество элементов, чтобы привести к частым переборам (или коллизии, если ваш HashSet не может изменять размер), HashSet, безусловно, дает вам преимущество постоянного доступа времени. Но на множестве с большим количеством роста или усадки вы можете получить лучшую производительность с Treesets, в зависимости от реализации.

    Амортизированное время может быть близко к O (1) с функциональным красно-черным деревом, если память мне помогает. У книги Окасаки было бы лучшее объяснение, чем я могу сделать. (Или см. Его список публикаций )

    Реализации HashSet, конечно, намного быстрее – меньше накладных расходов, потому что нет заказов. Хороший анализ различных реализаций набора в Java представлен на странице http://java.sun.com/docs/books/tutorial/collections/implementations/set.html .

    В обсуждении также указывается интересный подход «среднего звена» к вопросу «Дерево против хеширования». Java предоставляет LinkedHashSet, который представляет собой HashSet с «связанным с вложением» связанным списком, проходящим через него, то есть последний элемент связанного списка также является последним, вставленным в Hash. Это позволяет избежать нечистоты неупорядоченного hashа, не увеличивая стоимость TreeSet.

    TreeSet – это одна из двух отсортированных коллекций (другая – TreeMap). Он использует древовидную структуру Red-Black (но вы это знали) и гарантирует, что элементы будут в порядке возрастания, в соответствии с естественным порядком. Необязательно, вы можете построить TreeSet с конструктором, который позволяет вам предоставить коллекции свои собственные правила для того, что должен быть (а не полагаться на порядок, определенный classом элементов), используя Comparable или Comparator

    и LinkedHashSet – это упорядоченная версия HashSet, которая поддерживает двусвязный список для всех элементов. Используйте этот class вместо HashSet, когда вам нужен порядок итераций. Когда вы выполняете итерацию через HashSet, порядок непредсказуем, а LinkedHashSet позволяет вам перебирать элементы в том порядке, в котором они были вставлены

    Было дано много ответов на основе технических соображений, особенно в отношении производительности. По мне, выбор между TreeSet и HashSet имеет значение.

    Но я бы предпочел, чтобы выбор сначала определялся концептуальными соображениями.

    Если для объектов, которые вам нужно манипулировать, естественный порядок не имеет смысла, тогда не используйте TreeSet .
    Это сортированный набор, поскольку он реализует SortedSet . Таким образом, это означает, что вам нужно переопределить функцию compareTo , которая должна соответствовать тому, что возвращает функция equals . Например, если у вас есть набор объектов classа Student, то я не думаю, что TreeSet имеет смысл, поскольку между учениками нет естественного порядка. Вы можете заказать их по среднему classу, хорошо, но это не «естественный порядок». Функция compareTo возвращает 0 не только тогда, когда два объекта представляют одного и того же ученика, но также, когда два разных ученика имеют одинаковый class. Во втором случае equals вернет false (если вы не решите, что последнее вернет true, когда два разных ученика имеют одинаковый class, что сделает функцию equals имеет ошибочное значение, а не сказать неправильное значение).
    Обратите внимание, что эта согласованность между equals и compareTo является необязательной, но настоятельно рекомендуется. В противном случае договор интерфейса Set нарушен, что делает ваш код вводящим в заблуждение другим людям, что также может привести к неожиданному поведению.

    Эта ссылка может быть хорошим источником информации по этому вопросу.

    Почему есть яблоки, когда вы можете иметь апельсины?

    Серьезно парни и девушки – если ваша коллекция большая, читайте и записывайте на gazillions раз, и вы платите за циклы процессора, тогда выбор коллекции имеет значение ТОЛЬКО, если вам НЕТ это делать лучше. Однако в большинстве случаев это не имеет значения – несколько миллисекунд здесь и там остаются незамеченными в человеческих терминах. Если это действительно так важно, почему вы не пишете код на ассемблере или C? [cue другое обсуждение]. Таким образом, дело в том, что если вы счастливы использовать любую коллекцию, которую вы выбрали, и она решает вашу проблему (даже если это не самый лучший тип коллекции для задачи) выбивается из игры. Программное обеспечение является податливым. Оптимизируйте свой код там, где это необходимо. Дядя Боб говорит, что преждевременная оптимизация – это корень всего зла. Дядя Боб говорит так

    Message Edit ( полная переписывание ) Когда заказ не имеет значения, вот когда. Оба должны дать Log (n) – было бы полезно увидеть, превышает ли это на пять процентов быстрее, чем другое. HashSet может дать O (1) тестирование в цикле, чтобы показать, есть ли это.

     import java.util.HashSet; import java.util.Set; import java.util.TreeSet; public class HashTreeSetCompare { //It is generally faster to add elements to the HashSet and then //convert the collection to a TreeSet for a duplicate-free sorted //Traversal. //really? O(Hash + tree set) > O(tree set) ?? Really???? Why? public static void main(String args[]) { int size = 80000; useHashThenTreeSet(size); useTreeSetOnly(size); } private static void useTreeSetOnly(int size) { System.out.println("useTreeSetOnly: "); long start = System.currentTimeMillis(); Set sortedSet = new TreeSet(); for (int i = 0; i < size; i++) { sortedSet.add(i + ""); } //System.out.println(sortedSet); long end = System.currentTimeMillis(); System.out.println("useTreeSetOnly: " + (end - start)); } private static void useHashThenTreeSet(int size) { System.out.println("useHashThenTreeSet: "); long start = System.currentTimeMillis(); Set set = new HashSet(); for (int i = 0; i < size; i++) { set.add(i + ""); } Set sortedSet = new TreeSet(set); //System.out.println(sortedSet); long end = System.currentTimeMillis(); System.out.println("useHashThenTreeSet: " + (end - start)); } } 
    Давайте будем гением компьютера.