Set集合了解一下

  • 前言
  • Set 接口
  • Set 集合的常见实现类
    • HashSet
      • HashSet的属性及函数
    • TreeSet
      • TreeSet的属性和函数
    • LinkedHashSet
      • LinkedHashSet的属性和函数
  • 总结

前言

本文使用用的是jdk1.8

Set 接口

我们前面再Collection集合的文章中已经简单的说过Set集合的特点。

  • 不包含重复元素,即两个元素e1.equals(e2)不可相等。

需要注意的是:

当可变对象存储到Set集合中的时候,如果通过一种可以影响对象的equals函数的比较结果的方式改变了该对象的话那么Set集合的行为则是不确定的。

Set 集合的常见实现类

Set集合了解一下_第1张图片
image
  • HashSet:底层是通过HashMap来实现的,所以底层数据结构式是 散列表+红黑树 的数据结构。
  • TreeSet:底层数据结构是通过NavigableMap来实现的,底层数据结构是 红黑树。可保证元素的排列方式
  • LinkedHashSet:继承自HashSet,和LinkedHashMap意义相似,底层数据结构是 散列表+双向链表

HashSet

Set集合了解一下_第2张图片

总的来说:

  • 实现了Set接口,底层其实是一个HashMap实例,所以由于HashMap的特性,无法保证元素的迭代的顺序(HashMap扩容后的重散列会导致元素存储位置的变更)。同时和HashMap一样可以使用null元素做键值,但只能是一个。
  • 对Set结合进行迭代所需的时间与HashSet实例的大小(元素的数量)和底层HashMap实例(桶的数量)的“容量”的和成比例。如果某个Set的迭代性能比较重要,不要将初始容量设置的太高,或者将加载因子设置的太小(会出现很多空桶)。
  • 非同步,并发不安全。可以程序外部手动同步,也可以借助Collections.synchronizedSet函数来对该Set集合进行包装来实现同步。
  • 迭代器是快速失败的

HashSet的属性及函数

Set集合了解一下_第3张图片
image

一眼看上去还是很简单的。只有两个属性,一个是它具体实现类HsahMap,另一个则是一个Object实例。这个Object实例在这里是充当HashMap实例中所有元素的公共value的。因为我们HashSet底层是通过HashMap来实现的,所有元素实际都存储在key中,那么所对应的value如果不赋值则是null值得存在。所以HashSet的处理方式就是将所有元素的value都赋予该Object实例。

所以整个HashSet整体上来说几乎所有的操作都是在底层操作了一个HashMap实例。根据上图我们来看一下HashSet所实现的Set接口的函数都是怎么实现的。

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

public void clear() {
    map.clear();
}

public boolean contains(Object o) {
    return map.containsKey(o);
}

public Iterator iterator() {
    return map.keySet().iterator();
}

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

public int size() {
    return map.size();
}

所有的构造函数所做的也是统一的初始化HashMap的操作

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

public HashSet(Collection c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

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

TreeSet

Set集合了解一下_第4张图片

总的来说:

  • 底层实现:和HashSet实现方式一样,底层其实是由一个TreeMap实例来实现的。并实现了NavigableSet接口,是一个基于TreeMap的NavigableSet的实现
  • 有序:由于实现的NavigableSet接口继承自SortedSet接口,所以存入TreeSet的所有元素都必须是实现了Comparable接口的,即保证这些元素在存入TreeSet的时候在通过compareTo函数进行比较并自然排序的时候不会报ClassCastException。或者也可以在Set实例构造的时候通过传入的 Comparator来进行控制排序。
  • 非同步:并发线程不安全。还是老样子,倘若想同步需要函数外手动进行控制,或者在创建Set集合的时候借助Collections类。Collections.synchronizedSortedSet的方法来对某个Set集合进行包装。
  • 迭代器:迭代器是快速失败的
  • 复杂度:基本操作函数如add,remove,contains的复杂度都是log(n)级别的(TreeMap底层红黑树)。

TreeSet的属性和函数

Set集合了解一下_第5张图片
image

从图可以很明显的看出它的实现机制和HashSet是一模一样的。底层通过NavigableMap来实现,而NavigableMap则只有TreeMap这一个实现类。我们也可以称之为底层是由TreeMap来实现的,而Object实例则是充当所有元素的value的存在。

Set集合了解一下_第6张图片
image

我们来看一下TreeSet的5个构造函数都是如何实现的

public TreeSet() {
    this(new TreeMap());
}

public TreeSet(Collection c) {
    this();
    addAll(c);
}

public TreeSet(Comparator comparator) {
    this(new TreeMap<>(comparator));
}

TreeSet(NavigableMap m) {
    this.m = m;
}

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

还是清晰的。都是通过构造一个TreeMap来实现整个TreeSet的所有操作的。

LinkedHashSet

Set集合了解一下_第7张图片

总的来说:

  • 实现:LinkedHashSet底层由散列表和双向链表来实现的。这一点和LinkedHashMap一样。LinkedHashSet由HashSet和LinkedList共同来实现的。底层是HashMap,只是各节点Entry另外的还添加了两个属性before和after,通过双向列表来链接。
  • 迭代:迭代有序,由于所有元素都通过LinkedeList双向链表来维护,所以将按照元素插入到set中的顺序(插入顺序)进行迭代。需要注意的是如果一个元素已经存在于LinkedHashSet中如果再插入该元素的话,该元素会被重新插入。并且迭代的时候和HashSet容量没有关系。因为它结构中维护的一个双向链表,迭代的时候是迭代这个双向链表。相比于此HashSet迭代时则要受到容器容量很大的限制。
  • 优点
    • 使用LinkedHashSet可以不用像HashSet一样元素杂乱无序,而保证元素一定的有序性,可以做到TreeSet的功能却不用TreeSet的成本。
    • 初始容量的大小对LinkedHasSet的影响要比对HashSet小很多,因为它在迭代的时候不受容器大小的影响。
  • 缺点
    • 性能比HashSet要差一点儿,因为还要维护一个双向链表。
  • 允许null。非同步的,迭代是快速失败的

LinkedHashSet的属性和函数

Set集合了解一下_第8张图片
image

很纯粹的类,没有自己实现的东西。所有的函数都是继承自各个父类

我们来看一下它的构造函数:

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 c) {
    super(Math.max(2*c.size(), 11), .75f, true);
    addAll(c);
}

可以看出,都是去构造其父类HashSet。

总结

可以看到整个Set集合的实现都是Map映射。

  • HashSet:无序,允许为null,底层是HashMap(散列表+红黑树),非线程同步
  • TreeSet:有序,不允许为null,底层是TreeMap(红黑树),非线程同步
  • LinkedHashSet:迭代有序,允许为null,底层是HashMap+双向链表,非线程同步

你可能感兴趣的:(Set集合了解一下)