打赏

Java 集合系列之三:Set基本操作

1. Java Set

1. Java Set 重要观点

  • Java Set接口是Java Collections Framework的成员。
  • Set不允许出现重复元素-----------无重复
  • Set不保证集合中元素的顺序---------无序
  • Set允许包含值为null的元素,但最多只能有一个null元素。
  • Set支持泛型(类型的参数化),我们应尽可能使用它。将Generics与List一起使用将在运行时避免ClassCastException。
  • 先去看Map,Set的实现类都是基于Map来实现的(如,HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,LinkedHashSet是通过LinkedHashMap来实现的)。 

2. Java Set类图

Java Set接口扩展了Collection接口。Collection接口 externs Iterable接口。

一些最常用的Set实现类是HashSet,LinkedHashSet,TreeSet,SortedSet,CopyOnWriteArraySet。

AbstractSet提供了Set接口的骨干实现,以减少实现List的工作量。

 

3. Java Set 方法

 boolean    add(E e) //如果 set 中尚未存在指定的元素,则添加此元素(可选操作)。
 boolean    addAll(Collection<? extends E> c) //如果 set 中没有指定 collection 中的所有元素,则将其添加到此 set 中(可选操作)。
 void      clear() //移除此 set 中的所有元素(可选操作)。
 boolean    contains(Object o) //如果 set 包含指定的元素,则返回 true。
 boolean    containsAll(Collection<?> c) //如果此 set 包含指定 collection 的所有元素,则返回 true。
 boolean    equals(Object o) //比较指定对象与此 set 的相等性。
 int       hashCode() //返回 set 的哈希码值。
 boolean    isEmpty() //如果 set 不包含元素,则返回 true。
 Iterator<E>    iterator() //返回在此 set 中的元素上进行迭代的迭代器。
 boolean    remove(Object o) //如果 set 中存在指定的元素,则将其移除(可选操作)。
 boolean    removeAll(Collection<?> c) //移除 set 中那些包含在指定 collection 中的元素(可选操作)。
 boolean    retainAll(Collection<?> c) //仅保留 set 中那些包含在指定 collection 中的元素(可选操作)。
 int       size() //返回 set 中的元素数(其容量)。
 Object[]   toArray() //返回一个包含 set 中所有元素的数组。
<T> T[]    toArray(T[] a) //返回一个包含此 set 中所有元素的数组;返回数组的运行时类型是指定数组的类型。

2. HashSet

1. HashSet 结构图

HashSet,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null元素。!
HashSet继承了AbstractSet,实现了Cloneable和Serializable接口!

  • 实现了Cloneable接口,即覆盖了函数clone(),实现浅拷贝。
  • 实现了Serializable接口,支持序列化,能够通过序列化传输。

2. HashSet 重要特点

  1. 依赖于哈希表(实际上是一个 HashMap 实例)(哈希表+链表+红黑树),不可以存储相同元素(排重)
  2. 底层实现是一个HashMap(保存数据),实现Set接口。(HashSet中含有一个”HashMap类型的成员变量”map,HashSet的操作函数,实际上都是通过map实现的。)
  3. 非同步,线程不安全,存取速度快(同步封装Set s = Collections.synchronizedSet(new HashSet(...));
  4. 默认初始容量为16。
  5. 加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容
  6. 扩容增量:原容量的 1 倍,如 HashSet的容量为16,一次扩容后是容量为32
  7. 重写hashCode():HashSet集合排重时,需要判断两个对象是否相同,对象相同的判断可以通过hashCode值判断,所以需要重写hashCode()方法
  8. 重写equals():equals()方法是Object类中的方法,表示比较两个对象是否相等,若不重写相当于比较对象的地址, 所以我们可以尝试重写equals方法,检查是否排重。
  9. 会根据hashcode和equals来庞端是否是同一个对象,如果hashcode一样,并且equals返回true,则是同一个对象,不能重复存放。 
  10. fail-fast机制:HashSet通过iterator()返回的迭代器是fail-fast的。
  11. 两种遍历方法:Iterator【iterator.next()】,forEach【set.toArray();】
Set<String> set = new HashSet<String>();
set.add("first");
set.add("second");
set.add("three");

// foreach
for (String string : set) {
    System.out.println(string);
}

// iterator
Iterator<String> setIterator = set.iterator();
while (setIterator.hasNext()) {
    String string = (String) setIterator.next();
    System.out.println(string);
}

3. TreeSet

1. TreeSet 结构图

基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator进行排序,具体取决于使用的构造方法。

  TreeSet也不能存放重复对象,但是TreeSet会自动排序,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则。排序规则包括自然排序和客户排序。

  ①自然排序:TreeSet要添加哪个对象就在哪个对象类上面实现java.lang.Comparable接口,并且重写comparaTo()方法,返回0则表示是同一个对象,否则为不同对象。

  ②客户排序:建立一个第三方类并实现java.util.Comparator接口。并重写方法。定义集合形式为TreeSet ts = new TreeSet(new 第三方类());


TreeSet继承了AbstractSet,实现了NavigableSet、Cloneable和Serializable接口!

  • 继承于AbstractSet,AbstractSet实现了equals和hashcode方法。
  • 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
  • 实现了Cloneable接口,即覆盖了函数clone(),实现浅拷贝。
  • 实现了Serializable接口,支持序列化,能够通过序列化传输。
  •  TreeSet是SortedSet接口的实现类

2. TreeSet 重要特点

  1. 依赖于TreeMap,TreeSet是基于TreeMap实现的。(红黑树)复杂度为O(log (n))
  2. 不可以存储相同元素(排重),自动排序。(有序集合)
  3. TreeSet中不允许使用null元素!在添加的时候如果添加null,则会抛出NullPointerException异常。
  4. TreeSet是非同步的方法【SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));】。
  5. 它的iterator 方法返回的迭代器是fail-fast的。
  6. TreeSet不支持快速随机遍历,只能通过迭代器进行遍历! 两种遍历方法:Iterator【iterator.next()】,forEach【set.toArray();】
  7. TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
  8. 自然排序,重写compareTo方法:元素所属的类需要实现java.lang.Comparable接口,并重写compareTo方法。 compareTo方法除了可以进行排序外,还有排重的功能,但是必须在compareTo方法中对类中所有的属性值都进行判断,否则不比较那个属性,排重就会忽略哪个属性
  9. 定制排序,重写compare方法:元素需要通过java.util.Comparator接口(比较器)中的compare方法进行比较大小,并排序。 compare方法除了可以进行排序外,还有排重的功能,但是必须在compare方法中对类中所有的属性值都进行判断,否则不比较那个属性,排重就会忽略哪个属性

  ps:Comparable中的compareTo()一个参数, Comparator中compare()两个参数,返回值都是int类型,如果返回0,表示两个比较元素相同,如果大于0 ,前面大于后面,如果小于0,前面小于后面。

3. HashSet vs TreeSet

  1. HashSet是一个无序的集合,基于HashMap实现;TreeSet是一个有序的集合,基于TreeMap实现。
  2. HashSet集合中允许有null元素,TreeSet集合中不允许有null元素。
  3. HashSet和TreeSet都是非同步!在使用Iterator进行迭代的时候要注意fail-fast。

4. LinkedHashSet

1. LinkedHashSet 结构图

LinkedHashSet类:LinkedHashSet正好介于HashSet和TreeSet之间,它也是一个hash表,但它同时维护了一个双链表来记录插入的顺序,基本方法的复杂度为O(1)。

当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。

2. LinkedHashSet 重要特点

  1. 继承自HashSet,与HashSet唯一的区别是LinkedHashSet内部使用的是LinkHashMap((哈希表+链表+红黑树)+双向链表)。
  2.  LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
  3. 非同步,线程不安全,存取速度快(同步封装 Set s = Collections.synchronizedSet(new LinkedHashSet(...));
  4. 其他同HashSet
  5. 维护插入顺序,LinkedHashSet使用LinkedHashMap对象来存储它的元素,插入到LinkedHashSet中的元素实际上是被当作LinkedHashMap的键保存起来的
  6. LinkedHashMap的每一个键值对都是通过内部的静态类Entry<K, V>实例化的。这个 Entry<K, V>类继承了HashMap.Entry类。这个静态类增加了两个成员变量,before和after来维护LinkedHasMap元素的插入顺序。这两个成员变量分别指向前一个和后一个元素,这让LinkedHashMap也有类似双向链表的表现。

 

4. ConcurrentSkipListSet

1. ConcurrentSkipListSet 结构图

2. ConcurrentSkipListSet 重要特点

  1. 一个基于 ConcurrentSkipListMap 的可缩放并发 NavigableSet 实现。set 的元素可以根据它们的自然顺序进行排序,也可以根据创建 set 时所提供的 Comparator 进行排序,具体取决于使用的构造方法。
  2. 此实现为 containsaddremove 操作及其变体提供预期平均 log(n) 时间开销。多个线程可以安全地并发执行插入、移除和访问操作。迭代器是弱一致 的,返回的元素将反映迭代器创建时或创建后某一时刻的 set 状态。它们 抛出 ConcurrentModificationException,可以并发处理其他操作。升序排序视图及其迭代器比降序排序视图及其迭代器更快。
  3. 请注意,与在大多数 collection 中不同,这里的 size 方法不是 一个固定时间 (constant-time) 操作。由于这些 set 的异步特性,确定元素的当前数目需要遍历元素。此外,批量操作 addAllremoveAllretainAll 和 containsAll 并不 保证能以原子方式 (atomically) 执行。例如,与 addAll 操作一起并发操作的迭代器只能查看某些附加元素。
  4. 此类不允许使用 null 元素,因为无法可靠地将 null 参数及返回值与不存在的元素区分开来。

4. CopyOnWriteArraySet (JUC)

1. CopyOnWriteArraySet 结构图

2. CopyOnWriteArraySet 重要特点

  1. CopyOnWriteArraySet 是线程安全的 Set,它是由 CopyOnWriteArrayList 实现内部持有一个 CopyOnWriteArrayList 引用,所有的操作都是由 CopyOnWriteArrayList 来实现的,区别就是 CopyOnWriteArraySet 是无序的,并且不允许存放重复值。由于是一个Set,所以也不支持随机索引元素。
  2. 适合元素比较少,并且读取操作高于更新(add/set/remove)操作的场景
  3. 由于每次更新需要复制内部数组,所以更新操作(addset 和 remove 等等)开销比较大。
  4. 内部的迭代器 iterator 使用了不变的“快照”技术,存储了内部数组快照, 所以它的 iterator 不支持可变remove、set、add操作,但是通过迭代器进行并发读取时效率很高。
  5. 它是线程安全的。 

 

posted @ 2019-04-30 20:32  海米傻傻  阅读(9522)  评论(0编辑  收藏  举报