Java 集合框架(7)---- Set 相关类解析

本文标题大纲:

前言

在上篇文章中,我们将剩下的常见的 Map 接口下的相关具体类做了一个解析,还有一些相关的类将会在下一篇文章中做一个总结,这篇我们来看看 Set 接口的相关类。老规矩,还是继续看一下 Set 接口下继承关系图:

ListMap 接口很类似:Set 接口提供了两个子类和接口 AbstractSet 类和 SortedSet接口,类比之前介绍 Map 接口下的 AbstractMap 类和 SortedMap 接口,我们就可以知道:AbstractSet 类最大化的实现了 Set 接口中的一些抽象方法,使得其子类可以以最小的代价来实现一个 Set 具体类。而 SortedSet 则是提供了一个将元素按照某种规则排序的一种约定。在 SortedSet 中提供了一个方法:

/**
  * Returns the comparator used to order the elements in this set,
  * or <tt>null</tt> if this set uses the {@linkplain Comparable
  * natural ordering} of its elements.
  *
  * @return the comparator used to order the elements in this set,
  *         or <tt>null</tt> if this set uses the natural ordering
  *         of its elements
  */
Comparator<? super E> comparator();

很明显这是一个用于得到元素之间的比较器(Comparator)对象的一个方法,我们来看看其返回的 Comparator 接口:

public interface Comparator<T> {
	// ...
    /**
     * 用于比较两个元素大小的方法,如果返回正数,证明 o1 > o2,如果返回负数,
     * 则 o1 < o2,如果返回 0,证明 o1 == o2
     */
    int compare(T o1, T o2);
    // ...
}

这个接口关键的方法就是 compare 方法,其实在 TreeMap 中也用到了,因为 TreeMap 会将键值对元素按键的顺序排序,那么具体的排序规则就看你如何重写这个方法了。下面来看一下 Set 接口下一些具体的类:

HashSet

这个类用来尽量保证以 O(1) 的时间复杂度来添加/判断元素存在/移除元素等。在之前我们已经知道了 HashMap 的原理,而记得在第一篇介绍 Map 的文章中就已经说过为什么先介绍 Map 再介绍 Set ,我们直接来看看 HashSet 的源码吧,看完之后可能你就会觉得确实是这样的:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    public HashSet() {
        map = new HashMap<>();
    }

    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
	// !!! 注意这个方法,是个彩蛋
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

    /**
     * 返回当前集合迭代器(Iterator)对象,用于遍历集合中的元素
     */
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

    /**
     * 返回当前集合中元素的个数
     */
    public int size() {
        return map.size();
    }

    /**
     * 判断当前集合是否为空集合
     */
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /**
     * 判断对象 o 是否存在当前集合中(通过 equals 方法判断等价)
     */
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    /**
     * 向当前集合元素中添加一个元素 e
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    /**
     * 移除参数所代表的元素(通过 equals 方法比较等价),移除成功返回 true,否则返回 false
     */
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    /**
     * 清除当前 Set 集合中所有的元素
     */
    public void clear() {
        map.clear();
    }
    // ... 

大部分的常用方法就是这些(添加元素、移除元素、遍历…),我们可以看到,所有对元素的相关操作都交给了其内部的一个 HashMap 对象处理,而添加进 HashSet 中的元素其实都是作为“键”储存在了这个 HashMap 对象中,那么其对应的值呢?当然是那个 Object 类型的 PRESENT 对象啦。同时在返回遍历元素的迭代器的时候,也是返回了 HashMap 对象中对应的 keySet 的迭代器。这么一来,你只要知道了 HashMap 的运行机制,HashSet 对你来说就没有任何问题了。关于 HashMap 的运行机制,可以参考之前的文章:Java 集合框架(5)---- Map 相关类解析(中)

TreeSet

这个类可以使得添加进入 Set 集合中的元素按照某种规则来排序,但是其并不是直接实现 SortedSet 接口,而是实现了 SortedSet 的一个子接口 NavigableSet,其实 TreeMap 本身也没有直接实现 SortedMap 接口,而是实现了其的一个子接口 NavigableMap 。而和 HashSet 类似,其内部也是借助了一个 TreeMap 类型的对象来实现相关的操作。关于 TreeMap 可以参考:Java 集合框架(5)---- Map 相关类解析(中)

NavigableSet

先来看看 NavigableSet 的源码:

public interface NavigableSet<E> extends SortedSet<E> {
    // 返回当前集合中小于 e 的最大的元素
    E lower(E e);

    // 返回当前集合中不大于 e 的最大的元素
    E floor(E e);

    // 返回当前集合中不小于 e 的最小的元素
    E ceiling(E e);

    // 返回当前集合中大于 e 的最小的元素
    E higher(E e);

    // 返回并移除当前集合中的第一个(最小的)元素
    E pollFirst();

    // 返回并移除当前集合中的最后一个(最大的)元素
    E pollLast();

    // 按元素升序顺序返回遍历元素的迭代器
    Iterator<E> iterator();

    // 按元素降序顺序返回遍历另一个包含元素的集合
    NavigableSet<E> descendingSet();

    // 按元素降序顺序返回遍历元素的迭代器,和上面的 iterator 方法相反
    Iterator<E> descendingIterator();

    // 返回一个子集合,大于 fromElement 并小于 toElement,
    // 是否包含 fromElement 和 toElement 取决于 fromInclusive 和 toInclusive 参数是否为 true
    NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                           E toElement,   boolean toInclusive);
	// ...
}

可以看到 NavigableSet 接口声明了一些返回集合中一些特定元素/子集合的方法,下面来看看 TreeSet 的源码:

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{
    /**
     * The backing map. 用到的 TreeMap 对象
     */
    private transient NavigableMap<E,Object> m;

    // Dummy value to associate with an Object in the backing Map
    // 固定的值对象
    private static final Object PRESENT = new Object();

    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
	/**
	 * 构造方法,传入用于比较元素大小的 Comparator 类型的比较器。
	 * 如果元素类型实现了 Comparable 接口,那么不传这个参数也是可以的,
	 * TreeMap 会使用元素实现的 Comparable 接口中的 compareTo 来比较两个元素的大小
	 */
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }

    /**
     * 返回升序(相对 Comparator 的 compare 方法而言)遍历元素的迭代器
     */
    public Iterator<E> iterator() {
        return m.navigableKeySet().iterator();
    }

    /**
     *  返回逆序遍历元素的迭代器,和上一个方法相反
     */
    public Iterator<E> descendingIterator() {
        return m.descendingKeySet().iterator();
    }
    
    public int size() {
        return m.size();
    }

    public boolean isEmpty() {
        return m.isEmpty();
    }

    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;
    }

    public void clear() {
        m.clear();
    }
    
    // ...
    
    public E first() {
        return m.firstKey();
    }

    public E last() {
        return m.lastKey();
    }

    public E lower(E e) {
        return m.lowerKey(e);
    }

    public E floor(E e) {
        return m.floorKey(e);
    }
    
    // ...
}

同样的,也是内部借助了一个 TreeMap 对象来实现的相关方法。所以只要你理解 TreeMap 的运行原理,那么 TreeSet 对你来说也没有任何问题。下面来看一下最后一个 Set 的具体类:LinkedHashSet:

LinkedHashSet

就像 HashMapLinkedHashMap 的关系一样,LinkedHashSet 是继承了 HashSet 的,这个类的作用时保证遍历元素得到的元素序列的顺序和插入元素的先后顺序一样。而其中没有重写任何操作元素的方法,我们来看看:

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {

    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    public LinkedHashSet() {
        super(16, .75f, true);
    }

    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }
    // ...
}

第一眼看到这个类,我也有点吃惊: what!!?就提供了构造方法?没有重写任何一个操作元素的方法?那怎么来维持元素的相对顺序?(疑问三连)。但是我们注意到提供的三个构造方法都是调用了父类中具有 3 个参数的构造方法,那么我们赶紧去其父类(HashSet)中看看这个构造方法:

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

看到这里我终于松了口气,这里的构造方法创建的是 LinkedHashMap 对象,我们通过前面的篇幅已经知道 LinkedHashMap 是可以保证元素的遍历顺序是和元素插入顺序一样的,因为它就是做这个工作的。关于其运行元素可以参考 Java 集合框架(6)---- Map 相关类解析(下) 。我们继续,既然这里创建的是 LinkedHashMap 对象,而同时 HashSet 将所有操作元素的相关方法都交给了对应的 map 来做,那么在这里我们调用 LinkedHashSet 中的一些操作元素的方法其实都是调用的 LinkedHashMap 中的相关方法,那么自然可以保证元素的遍历顺序和插入顺序一致。

好了,对 Set 接口的接口就到这里了,因为这里用到的都是 Map 接口下的相关具体类,所以篇幅并不是很长,关键还是对相关 Map 接口下的相关具体类的理解。在下一篇文章中我们将一起对整个 Java 集合框架做一个总结,也算是深入 Java 集合框架的系列的一个结束篇吧。

如果文章中有什么不正确的地方,还请多多指点,如果觉得我写的对您有帮助,请不要吝啬您的赞。

谢谢观看。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值