Использовать HashSet поверх ArrayList для передачи намерений? - PullRequest
10 голосов
/ 17 июня 2009

Представьте себе, что мне нужно создать коллекцию элементов, где порядок мог или не мог иметь значение. Фактически все, что я планирую сделать, это использовать итератор. Я заметил, что большинство моих коллег используют ArrayList против LinkedHashSet / HashSet. Мой вопрос: если я знаю, что эти элементы должны быть уникальными, должен ли я использовать набор или список? По сути, это не имеет большого значения, но разве Сет более эффективно не говорит о том, что элементы уникальны?

Я считаю, что это интересный вопрос для крупных корпоративных приложений по нескольким причинам: 1) Если вы не можете гарантировать качество кода в целом, использование набора может быть опасным. Зачем? Потому что equals () и хэш-код могут быть неправильно переопределены, и поэтому использование Set может вызвать некоторые действительно неприятные проблемы. 2) Использование списка более устойчиво к будущим изменениям. Если дубликаты по какой-либо причине становятся возможными, не нужно беспокоиться.

По сути, это сводится к следующему: если я знаю, что ожидать уникальных элементов, должен ли я отдавать предпочтение Set over List во всех случаях?

Редактировать: Полагаю, я также спрашиваю: следует ли использовать Set для , чтобы убедиться, , что дубликаты не добавлены, или его также можно использовать с единственной целью , иллюстрирующим Для простоты понимания дубликаты отсутствуют?

Ответы [ 10 ]

7 голосов
/ 17 июня 2009

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

2 голосов
/ 17 июня 2009

Если вам нужно думать об уникальных элементах, используйте Set. Но если вы не доверяете своим пользователям правильно реализовать equals / hashCode, то я предлагаю вам документально подтвердить, что если что-то не так с итерацией, проверьте ваш equals / hashCode! Но это действительно зависит от варианта использования модели данных.

1 голос
/ 07 июня 2012
    import java.util.*;

    public class Test {
        public void testHashSetAddition() {
            for(int mod=10; mod <= 100; mod=mod+10 ) {
                Set s = new HashSet();
                long start = new Date().getTime();
                for(int i=0; i<100000; i++) {
                    s.add(new Foo(i % mod));
                }
                System.out.println(s.size());
                long end = new Date().getTime();
                System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
            }
        }
        public void testAddingToArrayList() {
            for(int mod=100; mod >= 10; mod=mod-10 ) {
                List l = new ArrayList();
                long start = new Date().getTime();
                for(int i=0; i<100000; i++) {
                    l.add(new Foo(i % mod));
                }
                System.out.println(l.size());
                long end = new Date().getTime();
                System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
            }
        }

        public static void main(String...a){
            new Test().testHashSetAddition();
            new Test().testAddingToArrayList();
        }
        class Foo {
            private int hc;
            public Foo(int i) {
                this.hc = i;
            }
            public int hashCode() {
                return hc;
            }
            public int getHc(){
                return hc;
            }
            public boolean equals(Object o){
                if(!(o instanceof Foo)) return false;
                Foo fo = (Foo)o;
                return fo.getHc() == this.hc;
            }
        }

    }
/*
10
Mod: 10 - 31ms
20
Mod: 20 - 16ms
30
Mod: 30 - 15ms
40
Mod: 40 - 16ms
50
Mod: 50 - 0ms
60
Mod: 60 - 16ms
70
Mod: 70 - 0ms
80
Mod: 80 - 15ms
90
Mod: 90 - 0ms
100
Mod: 100 - 0ms
100000
Mod: 100 - 32ms
100000
Mod: 90 - 31ms
100000
Mod: 80 - 31ms
100000
Mod: 70 - 31ms
100000
Mod: 60 - 32ms
100000
Mod: 50 - 15ms
100000
Mod: 40 - 31ms
100000
Mod: 30 - 32ms
100000
Mod: 20 - 15ms
100000
Mod: 10 - 32ms
*/
1 голос
/ 17 июня 2009

Кто-то сказал, что HashSet обеспечивает постоянную производительность при добавлении, удалении, содержании и размере.

Фактический оператор в JavaDocs: «Этот класс обеспечивает постоянную производительность по времени для основных операций (добавление, удаление, содержание и размер), при условии, что хеш-функция правильно распределяет элементы между сегментами ».

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

Следующий код демонстрирует, что может произойти в зависимости от вашей реализации hashCode.

public void testHashSetAddition() {
    for(int mod=10; mod <= 100; mod=mod+10 ) {
        Set s = new HashSet();
        long start = new Date().getTime();
        for(int i=0; i<100000; i++) {
            s.add(new Foo(i % mod));
        }
        long end = new Date().getTime();
        System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
    }
}

class Foo {
    private int hc;
    public Foo(int i) {
        this.hc = i;
    }
    public int hashCode() {
        return hc;
    }
}

Результаты были следующими:

Mod: 10 - 22683ms
Mod: 20 - 14200ms
Mod: 30 - 10486ms
Mod: 40 - 8562ms
Mod: 50 - 7761ms
Mod: 60 - 6740ms
Mod: 70 - 5778ms
Mod: 80 - 5268ms
Mod: 90 - 4716ms
Mod: 100 - 3966ms

Затем выполните точно такой же тест для ArrayList:

public void testAddingToArrayList() {
    for(int mod=100; mod >= 10; mod=mod-10 ) {
        List l = new ArrayList();
        long start = new Date().getTime();
        for(int i=0; i<100000; i++) {
            l.add(new Foo(i % mod));
        }
        long end = new Date().getTime();
        System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
    }
}

Дает:

Mod: 100 - 50ms
Mod: 90 - 30ms
Mod: 80 - 40ms
Mod: 70 - 30ms
Mod: 60 - 30ms
Mod: 50 - 40ms
Mod: 40 - 20ms
Mod: 30 - 30ms
Mod: 20 - 30ms
Mod: 10 - 30ms
1 голос
/ 17 июня 2009

Учитывайте также удобочитаемость кода.

Если вы ожидаете и хотите уникальный набор, а затем используйте структуру данных «SET», в долгосрочной перспективе все будет намного яснее. И, таким образом, это также будет способствовать лучшему кодированию.

0 голосов
/ 10 мая 2011

@ Andrzej Doyle - я не думаю, что когда вы добавляете элемент в набор, выполняется повторное сравнение. Набор внутренне использует hashMap, поэтому любой дублирующий ключ будет переопределен, и, следовательно, никакой специальной проверки

0 голосов
/ 10 мая 2011

@ Andrzej Doyle - я не думаю, что когда вы добавляете элемент в набор, выполняется повторное сравнение. Набор внутренне использует hashMap, поэтому любой дублирующий ключ будет переопределен, и, следовательно, никакой специальной проверки не будет

0 голосов
/ 17 июня 2009

Я не думаю, что какой-либо из вариантов должен рассматриваться для передачи намерения - ваш метод должен быть объявлен как возвращающий просто Collection с соответствующим универсальным параметром, как для гибкости, так и потому, что, как вы сказали, потребители этого должен иметь возможность просто перебирать его, не беспокоясь о его типе. Это дает дополнительное преимущество: если требования изменятся позже или окажется, что по какой-то причине ваш первоначальный выбор был неверным, вам нужно изменить код только в одном месте (первоначальный вызов конструктора).

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

И я также согласен с приведенными выше постами, в которых говорится, что ваши рассуждения вокруг пункта 1) отключены - если есть классы с неправильными реализациями equals и / или hashcode, которые вы хотите поместить в набор, вы исправляете их, а затем использовать набор!

0 голосов
/ 17 июня 2009

Использование реализации Set над реализацией List может снизить производительность. При вставке элемента в набор необходимо убедиться, что он не является дубликатом. Если вы планируете просто использовать итератор, используйте простейшую возможную реализацию (ArrayList).

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

0 голосов
/ 17 июня 2009

Установите, если предпочтительнее, так как это обеспечит уникальность и покажет вам, где вы ошибаетесь.

У вас могут быть некоторые проблемы, когда методы неправильно переопределены, но правильный выбор - не молиться и не вызывать их. Обнаружьте ошибки и исправьте их!

Редактировать: И да, яснее, когда вы видите Set, уникальные значения необходимы и даже лучше: применяются уникальные значения. Никогда не угадывайте / не доверяйте использованию вашего кода;)

...