HashSet vs ArrayList содержит производительность

при обработке больших объемов данных я часто делаю следующее:

HashSet<String> set = new HashSet<String> ();
//Adding elements to the set
ArrayList<String> list = new ArrayList<String> (set);

что-то вроде "сброса" содержимого набора в списке. Я обычно делаю это, так как элементы, которые я добавляю, часто содержат дубликаты, которые я хочу удалить, и это кажется простым способом их удаления.

имея в виду только эту цель (избегая дубликатов), я мог бы также написать:

ArrayList<String> list = new ArrayList<String> ();
// Processing here
if (! list.contains(element)) list.add(element);
//More processing here

и "сброс" установить в список. Однако, Я бы сделал небольшую проверку перед вставкой каждого элемента (что, я предполагаю, делает и HashSet)

является ли какая-либо из двух возможностей более эффективной?

5 ответов


набор даст гораздо лучшую производительность (O(n) vs O(n^2) для списка), и это нормально, потому что установить принадлежность (contains операции) является цель набора.

содержится в HashSet is O(1) по сравнению с O(n) для списка, поэтому вы никогда не должны использовать список, если вам часто приходится работать contains.


на ArrayList использует массив для хранения данных. The ArrayList.contains будет иметь сложность O(n). Поэтому по существу поиск в массиве снова и снова будет иметь O(n^2) сложности.

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


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

вы можете сделать как вам нужен список без дубликатов.

private Set<String> set = new HashSet<>();
private List<String> list = new ArrayList<>();


public void add(String str) {
    if (set.add(str))
        list.add(str);
}

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


Я сделал тест, поэтому, пожалуйста, проверьте результат:

для тех же строковых элементов в HashSet, TreeSet, ArrayList и LinkedList, вот результаты для

  1. 50.000 идентификаторы UUID
    • искомый предмет : e608c7d5-c861-4603-9134-8c636a05a42b (индекс 25.000)
    • поиска HashSet.содержит(пункт) ? TRUE 0 ms
    • treeSet.содержит(пункт) ? TRUE 0 ms
    • класса ArrayList.содержит(пункт) ? Правда 2 мс
    • linkedList.содержит(пункт) ? TRUE 3 ms
  2. 5.000.000 идентификаторы UUID
    • искомый предмет: 61fb2592-3186-4256-a084-6c96f9322a86 (индекс 25.000)
    • поиска HashSet.содержит(пункт) ? TRUE 0 ms
    • treeSet.содержит(пункт) ? TRUE 0 ms
    • класса ArrayList.содержит(пункт) ? TRUE 1 ms
    • linkedList.содержит(пункт) ? Правда 2 мс
  3. 5.000.000 идентификаторы UUID
    • искомый элемент: db568900-c874-46ba-9b44-0e1916420120 (индекс 2.500.000)
    • поиска HashSet.содержит(пункт) ? TRUE 0 ms
    • treeSet.содержит(пункт) ? TRUE 0 ms
    • класса ArrayList.содержит(пункт) ? Правда 33 мс
    • linkedList.содержит(пункт) ? Правда 65 мс

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

    public static void main(String[] args) {
        Set<String> hashSet = new HashSet<>();
        Set<String> treeSet = new TreeSet<>();
        List<String> arrayList = new ArrayList<>();
        List<String> linkedList = new LinkedList<>();

        List<String> base = new ArrayList<>();

        for(int i = 0; i<5000000; i++){
            if(i%100000==0) System.out.print(".");
            base.add(UUID.randomUUID().toString());
        }

        System.out.println("\nBase size : " + base.size());
        String item = base.get(25000);
        System.out.println("SEARCHED ITEM : " + item);

        hashSet.addAll(base);
        treeSet.addAll(base);
        arrayList.addAll(base);
        linkedList.addAll(base);

        long ms = System.currentTimeMillis();
        System.out.println("hashSet.contains(item) ? " + (hashSet.contains(item)? "TRUE " : "FALSE") + (System.currentTimeMillis()-ms) + " ms");
        System.out.println("treeSet.contains(item) ? " + (treeSet.contains(item)? "TRUE " : "FALSE") + (System.currentTimeMillis()-ms) + " ms");
        System.out.println("arrayList.contains(item) ? " + (arrayList.contains(item)? "TRUE " : "FALSE") + (System.currentTimeMillis()-ms) + " ms");
        System.out.println("linkedList.contains(item) ? " + (linkedList.contains(item)? "TRUE " : "FALSE") + (System.currentTimeMillis()-ms) + " ms");
    }

вы можете добавить элементы в сам список. Затем, чтобы дедуп -

HashSet<String> hs = new HashSet<>(); // new hashset
hs.addAll(list); // add all list elements to hashset (this is the dedup, since addAll works as a union, thus removing all duplicates)
list.clear(); // clear the list
list.addAll(hs); // add all hashset elements to the list

Если вам просто нужен набор с dedup, вы также можете использовать addAll () на другом наборе, так что он будет иметь только уникальные значения.