Для поиска HashSet против Treeset

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

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

13 ответов


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

поиска HashSet

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

TreeSet

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

важно очки:

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

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

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

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

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

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


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

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

С javadocs для TreeSet:

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


1.HashSet позволяет нулевой объект.

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

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

например

 TreeSet<String> ts = new TreeSet<String>();
 ts.add(null); // throws NullPointerException

 HashSet<String> hs = new HashSet<String>();
 hs.add(null); // runs fine

основываясь на прекрасные визуальный ответ на картах @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 заключается в том, что операции (в среднем) O(1) вместо O(log n). Если набор содержит стандартные элементы, вы не будете "возиться с хэш-функциями", как это было сделано для вас. Если набор содержит пользовательские классы, необходимо реализовать hashCode использовать HashSet (хотя эффективная Java показывает, как), но если вы используете TreeSet вы должны сделать это Comparable или поставить Comparator. Это может быть проблемой, если класс не имеет особого порядок.

Я иногда использовал TreeSet (или на самом деле TreeMap) для очень маленьких наборов/карт (

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


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

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


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

обсуждение там также указывает на интересный подход "середины" к дереву против хэш-вопроса. Java предоставляет LinkedHashSet, который является HashSet с" ориентированным на вставку " связанным списком запуск через него, то есть последний элемент в связанном списке также является самым последним вставленным в хэш. Это позволяет избежать неупорядоченности неупорядоченного хэша без увеличения стоимости набора деревьев.


на TreeSet является одной из двух отсортированных коллекций (другая TreeMap). Он использует красно-черную древовидную структуру (но вы это знали) и гарантирует что элементы будут в порядке возрастания, в соответствии с естественным порядком. Необязательно, вы можете создать TreeSet с конструктором, который позволяет создавать коллекции ваших собственные правила для того, каким должен быть порядок (а не полагаться на определенный порядок по классу элементов) с использованием сопоставимого или Компаратор!--3-->

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


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

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

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

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


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

серьезно ребята и девочки-если ваша коллекция большая, читать и писать в газиллионы раз, и вы платите за циклы процессора, то выбор коллекции актуален только в том случае, если вам нужно, чтобы работать лучше. Однако в большинстве случаев это не имеет значения - несколько миллисекунд то тут, то там остаются незамеченными в человеческом понимании. Если это действительно так важно, почему вы не пишете код в ассемблере или C? [cue another обсуждение.] Так что дело в том, если вы счастливы использовать любую коллекцию, которую вы выбрали, и это решает вашу проблему [даже если это не специально лучший тип коллекции для задачи]. Программное обеспечение является гибким. При необходимости оптимизируйте код. Дядя Боб говорит, что преждевременная оптимизация-корень всех зол. дядя Боб так говорит


Сообщение Редактировать ( полностью переписанный ), когда порядок не имеет значения, вот когда. Оба должны дать 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<String> sortedSet = new TreeSet<String>();

        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<String> set = new HashSet<String>();

        for (int i = 0; i < size; i++) {
            set.add(i + "");
        }

        Set<String> sortedSet = new TreeSet<String>(set);
        //System.out.println(sortedSet);
        long end = System.currentTimeMillis();

        System.out.println("useHashThenTreeSet: " + (end - start));
    }
}