Java 集合框架【4】Set 体系

165 阅读3分钟

Set

这个接口模拟了数学集合抽象,是一个不包含重复元素的集合。更贴切地说,存在 e1e2 两个元素,满足 e1.equals(e2) ,Set 会认为它们是同一个。并且 Set 最多保存一个 null 元素。不允许包含重复元素意味着将可变对象作为元素时,需要格外小心。

相比 List 接口,Set 少了一些索引方法,所以 Set 接口没有顺序的概念。

Set 接口的常见实现包括:

  • HashSet
  • TreeSet
  • LinkedHashSet
  • SortedSet (子接口)
  • NavigableSet (SortedSet 的子接口)

HashSet

HashSet 直接实现了 Set 接口,底层实现实际上是一个 HashMap 。

    private transient HashMap<E,Object> map;
    private static final Object PRESENT = new Object();
    public HashSet() {
        map = new HashMap<>();
    }

因为底层实际上是 HashMap ,所以它是无序的,并且允许 null 元素。负载因子也是 0.75 。

它的操作方法:

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

以保存的元素为 Key ,默认 value 是一个 Object 对象。

其他关于 HashMap 的原理参见 《散列表及其在JDK中的实现》

TreeSet

TreeSet 的继承结构比 HashSet 复杂:

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable
​
public interface NavigableSet<E> extends SortedSet<E>
​
public interface SortedSet<E> extends Set<E>

SortedSet

这个接口继承自 Set ,拓展了对元素排序的能力,可以通过自然存入顺序或特定的比较器进行排序。插入有序集合的所有元素都必须实现 Comparable 接口。

所有通用的排序集合实现类都应该有四个标准的构造函数:

  1. 一个无参构造函数,创建一个空集合,并按照元素自然顺序排序。
  2. 一个具有 Comparator 类型的单个参数的构造函数,创建一个空集合,并根据指定的比较器进行排序。
  3. 一个具有 Collection 类型的单个参数的构造函数,创建一个新的有序集合,该集合具有与其参数类型相同的元素,并根据自然顺序排序。
  4. 一个具有 SortedSet 类型单个参数的构造函数,创建一个新的排序集合,该排序集合具有与参数集合相同的元素和顺序。

NavigableSet

使用一些导航方法拓展了 SortedSet ,返回给定目标的附近的元素。方法 lowerfloorceilinghigher 分别返回小于、小于或等于、大于或等于、大于给定元素的元素。如果不存在返回 null

可以按照升序或降序访问或遍历 NavigableSet 。此接口还定义了 pollFirstpollLast 方法,它们返回并删除最低的和最高的元素。

在允许 null 元素的集合中,上面的导航方法可能会返回不确定的值,然而在这种情况下,结果也可以通过 contains(null) 来消除歧义。为了避免此类问题,还是鼓励此接口的实现不允许插入 null 。(请注意,Comparable元素的排序集本质上不允许为空。)

TreeSet

基于 TreeMapNavigableSet 实现。 元素使用它们的自然顺序或在集合创建时提供的 Comparator 进行排序,具体取决于使用的构造函数。 此实现为基本操作(添加、删除和包含)提供有保证的 log(n) 时间成本。

TreeSet 的底层实现是

    private transient NavigableMap<E,Object> m; // TreeMap 是 NavigableMap 的实现
​
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

它的所有操作方法,也都是真正通过调用 TreeMap 对应的操作方法来实现的:

    public boolean contains(Object o) {
        return m.containsKey(o);
    }
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
    public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
    }

TreeMap

这里只简单介绍一下它的特性。

基于红黑树的 NavigableMap 实现,可以根据 Key 进行自然排序,也可通过比较器指定排序条件。

此实现为 containsKey、get、put 和 remove 操作提供有保证的 log(n) 时间成本。 算法是对 Cormen、Leiserson 和 Rivest 的算法简介中的那些算法的改编。

该实现并不是线程安全的。

总结

  • Set 一般要求元素不能重复
  • 有序的 Set 可以通过继承 SortedSet 接口来实现,Set 接口本身没定义排序能力。
  • 底层实现基本都是 Map ,代理了 Map 的实际操作方法。