java集合之Set接口

时间:2020-12-03 17:58:17

  Set集合通常不能记住元素添加的顺序,其他的操作和它的父接口基本相同。只是行为上有细微的差别,Set集合不能包含相同的元素。如果尝试添加相同的元素,调用add()方法将返回false,且新元素不能被加入容器。

下面是Set接口的三个实现类,将一一介绍:

1. HashSet

HashSet 类具备以下特征:

1. 不能保证元素的排列顺序,可能和添加的顺序不相同,也有可能会发生变化。
2. HashSet不是线程安全的,如果多个线程同时访问HashSet,假设有两个以上的线程修改了HashSet集合时,在程序中必须要做同步处理。
3. 集合元素值可以是null。

在源码中,HashSet的实现调用HashMap的

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    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<>();
    }
    ... //省略其他源码
}

 

  也就是说,当想HashSet中存入一个对象时,将调用该对象的hashCode()方法来的到该对象的hashCode值,然后根据hashCode值来得到该对象在HashSet中的位置。如果两个对象的hashCode值不同,就算是同一个对象,HashSet也会将他们存放在不同的位置,也就是说,HashSet集合判断两个元素相等的标准是两个对象通过调用equals()方法返回true,并且两个对象的hashCode()方法返回值也相等。因此,当我们在把自定义的对象放入HashSet的时候,都需要重写这个对象发equals()方法和hashCode()方法,重写hashCode方法的规则如下:

  1. 在程序运行中,同一个对象多次调用hashCode()方法的返回是一致的。
  2. 当两个对象的equals()方法返回为true时,hashCode()方法的返回值也应该相等。
  3. equals()方法中用作比较标准的实例变量,都应该用于计算hashCode值。

2. LinkedHashSet

  LinkedHashSet是HashSet的子类,它也是根据元素的hashCode值来计算元素的存储位置的,并且同时使用链表来维护元素的次序,这样使得元素看起来像是有序的(插入顺序),当我们遍历LinkedHashSet中的元素时,将按照元素的插入顺序来访问集合中的元素。
  LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但是迭代Set中的所有的元素时,会有较好的性能,因为它是以链表来维护元素的顺序的。

3. TreeSet

  • TreeSet是SortedSet接口的实现类,如名所示,TreeSet中的元素时有序的(大小序,而不是插入序)。

  • 与HashSet集合采用hashCode算法来决定元素的存储位置不同,TreeSet采用的是红黑树的数据结构来存储集合元素。但是TreeSet集合中的元素时按照上面规则来排序的呢?目前主持两种排序方式:

    1. 自然排序:加入TreeSet中的对象都实现Comparable接口,重写compareTo(Object obj)方法。同时需要保证加入TreeSet的而元素时同一类对象。
    2. 定制排序:加入TreeSet中的对象都实现Comparator接口,重写compare(T o1,T o2)方法。

4. EnumSet

  • EnumSet是一个专为枚举类型设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet枚举类型的时候显式或者隐式的指定。EnumSet集合的元素也是有序的,顺序是枚举定义的类型。
  • EnumSet的内部以向量的形式存储,这样占用的空间小,效率高。EnumSet中不允许加入null值。

总结:

各Set实现类的性能分析

  1. HashSet和TreeSet是Set接口的两个典型的实现,如何选择呢?HashSet的性能总是比TreeSet好(特别是频繁的添加,查询操作),因为TreeSet需要额外的红黑树算法来维护集合的次序,只有需要集合有序的时候才使用TreeSet,否则都使用HashSet。
  2. HashSet 和其子类LinkedHashSet,对于普通的插入,删除操作,LinkedHashSet比HashSet要慢一些,因为LinkedHashSet需要维护链表额外开销,但是当需要遍历整个容器时,LinkedHashSet会更快。
  3. EnumSet是这几个实现当中性能最好的,但是EnumSet集合中只能存放枚举类的枚举值。

  注:Set接口中的所有实现HashSet、TreeSet、EnumSet、LinkedHashSet都不是线程安全的,当有两个以上线程同时修改时,必须在程序中手动实现同步性(通常可以使用Collections工具类中的synchronizedSortedSet方法来包装Set集合,最好是在创建的时候设置)。