10

想象一下,我需要创建一个元素集合,其中顺序可能重要,也可能不重要。实际上,我计划做的就是使用迭代器。我注意到我的大多数同事都使用 ArrayList 与 LinkedHashSet/HashSet。我的问题是,如果我知道这些元素应该是唯一的,我应该使用 Set 还是 List?实际上它并没有真正产生影响,但是 Set 不是更有效地传达了元素是独一无二的吗?

我发现这对于大型企业应用程序来说是一个有趣的问题,原因如下: 1) 如果您不能保证整体代码的质量,那么使用 Set 可能会很危险。为什么?因为 equals() 和 hashcode 可能被错误地覆盖,因此使用 Set 可能会导致一些非常讨厌的问题。2) 使用列表更能适应未来的变化。如果出于某种原因可能出现重复,则无需担心。

基本上它归结为:如果我知道我应该期待独特的元素,我应该在所有情况下都支持 Set 而不是 List 吗?

编辑:我想我也在问:是否应该使用 Set 来确保不添加重复项,或者它是否也可以仅用于说明不存在重复项以便于理解?

4

10 回答 10

7

1)完全是假的。不要解决错误,修复它们。因此,如果顺序无关紧要,请使用任何Set实现,如果顺序很重要,请使用SortedSet。如果元素不必是唯一的(并且您现在应该确定这一点,并且它通常不应该改变),请随意使用List

于 2009-06-17T08:39:21.040 回答
2

如果您需要考虑独特的元素,请使用 Set。但是,如果您不相信您的用户能够正确实现 equals/hashCode,那么我建议您记录一下,如果迭代有问题,请检查您的 equals/hashCode!但这实际上取决于数据模型的用例。

于 2009-06-17T08:40:17.653 回答
1
    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
*/
于 2012-06-07T03:11:55.310 回答
1

还要考虑代码的可读性。

如果您期望并想要一个唯一的集合,那么使用“SET”数据结构,从长远来看,事情会更加清晰。因此,这也将促进更好的编码。

于 2009-06-17T08:43:05.620 回答
1

有人说 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
于 2009-06-17T14:56:48.907 回答
0

设置是否更可取,因为它将强制执行唯一性并向您显示错误所在。

当方法被错误地覆盖时,您可能会遇到一些问题,但正确的选择是不祈祷并避免调用它们。检测错误并修复它们!

编辑:是的,当您看到 Set 时会更清楚,需要唯一值,甚至更好:强制执行唯一值。永远不要猜测/相信你的代码的使用;)

于 2009-06-17T08:38:49.513 回答
0

我认为这两种选择都不应该被视为传达意图——你的方法应该被声明为只返回Collection带有适当通用参数的 a ,这既是为了灵活性,也是因为正如你所说,它的使用者应该能够迭代不用担心它是什么类型。这带来了额外的优势,即如果需求稍后发生变化,或者结果证明您的初始选择出于某种原因是错误的,您只需在一个地方(初始构造函数调用)更改代码。

其意图应该在方法的文档中指定,该文档应该详细说明集合的迭代器是否会以任何特定顺序返回元素,以及是否会出现重复元素。

而且我也同意上面的帖子说你对第 1 点的推理是错误的——如果有类的实现不正确equals和/或hashcode你想放入一个集合中,你修复它们然后使用一个集合!

于 2009-06-17T09:16:37.800 回答
0

@Andrzej Doyle——我不认为当你向一个集合添加一个元素时,重复比较就完成了。一个集合在内部使用hashMap,所以任何重复的键都将被覆盖,因此没有特定的检查

于 2011-05-10T10:20:37.747 回答
0

@Andrzej Doyle——我不认为当你向一个集合添加一个元素时,重复比较就完成了。一个集合在内部使用hashMap,所以任何重复的键都将被覆盖,因此没有特定的检查

于 2011-05-10T10:17:33.863 回答
-1

在 List 实现上使用 Set 实现可能会降低性能。在 Set 中插入元素时,您需要检查它是否重复。如果您打算只使用迭代器,请使用最简单的实现(ArrayList)。

我认为仅使用 Set 来传达信息并不是一个好主意。如果您自己添加项目并且可以保证不会添加重复项,那么使用 Set 毫无意义。使用适当的名称来传达有关集合的信息。此外,通过 Collection 接口公开它是一个好主意,特别是如果您的类的调用者只需要遍历集合。

于 2009-06-17T08:46:20.667 回答