Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
Set
实现分为通用和专用实现。
有三种通用的 Set
实现 HashSet
,TreeSet
,LinkedHashSet
。使用这三种中的哪一种通常是直截了当的。HashSet
比 TreeSet
快得多(大多数操作的常量时间与 log 时间),但不提供排序保证。如果需要使用 SortedSet
接口中的操作,或者需要按值进行迭代,请使用 TreeSet
;否则,请使用 HashSet
。可以肯定的是,大多数时候你最终都会使用 HashSet
。
LinkedHashSet
在某种意义上介于 HashSet
和 TreeSet
之间。作为一个具有链表的哈希表来实现,它提供 insertion-ordered (插入顺序) 迭代(最早插入到最近插入)并且运行几乎与 HashSet
一样快。LinkedHashSet
实现使其客户端免于由 HashSet
提供的未指定的、通常是混乱的排序影响,而不会导致与 TreeSet
相关的增加的成本。
关于 HashSet
值得记住的一件事是,迭代在条目数和桶数(capacity (容量))之和中是线性的。因此,选择太高的初始容量会浪费空间和时间。另一方面,选择一个太低的初始容量会在每次强制增加容量时复制数据结构,从而浪费时间。如果未指定初始容量,则默认值为 16。过去,选择素数作为初始容量有一些优势。这不再是真的。在内部,容量总是四舍五入到 2 的幂。使用 int
构造函数指定初始容量。以下代码行分配一个 HashSet
,其初始容量为 64。
Set<String> s = new HashSet<String>(64);
HashSet
类有另一个调整参数,称为 load factor (加载因子)。如果你非常关心 HashSet
的空间消耗,请阅读 HashSet
文档以获取更多信息。否则,只需接受默认值;这几乎总是正确的事情。
如果你接受默认加载因子但想要指定初始容量,请选择一个大约是你希望该组增长的大小的两倍的数字。如果你的猜测偏远,你可能会浪费一些空间,时间或两者,但这不太可能是一个大问题。
LinkedHashSet
与 HashSet
具有相同的调整参数,但迭代时间不受容量影响。TreeSet
没有调整参数。
有两个专用的 Set
实现 EnumSet
和 CopyOnWriteArraySet
。
EnumSet
是枚举类型的高性能 Set
实现。枚举集的所有成员必须具有相同的枚举类型。在内部,它由位向量表示,通常是单个 long
。枚举集支持枚举类型范围内的迭代。例如,给定星期几的枚举声明,你可以迭代工作日。EnumSet
类提供了一个简单的静态工厂。
for (Day d : EnumSet.range(Day.MONDAY, Day.FRIDAY)) System.out.println(d);
枚举集还为传统的位标志提供了丰富的,类型安全的替代品。
EnumSet.of(Style.BOLD, Style.ITALIC)
CopyOnWriteArraySet
是一个由写时复制数组支持的 Set
实现。所有的可变操作,例如 add
,set
和 remove
,都是通过制作数组的新副本来实现的;不需要锁定。甚至迭代也可以安全地与元素插入和删除同时进行。与大多数 Set
实现不同,add
,remove
和 contains
方法需要的时间与集合的大小成正比。此实现 仅 适用于很少修改但经常迭代的集。它非常适合维护必须防止重复的事件处理程序列表。