Set集合

Set

  • public interface Set<E>
    extends Collection<E>

    不包含重复元素的集合。更正式地,集合不包含一对元素e1e2 ,使得e1.equals(e2) ,并且最多一个空元素。正如其名称所暗示的那样,这个接口模拟了数学抽象。

    Set接口除了继承自Collection接口的所有构造函数的合同以及add,equalshashCode方法的合同外 增加了其他规定。 其他继承方法的声明也包括在这里以方便。 (伴随这些声明的规范已经量身定做Set接口,但它们不包含任何附加的规定。)

    构造函数的额外规定并不奇怪,所有构造函数都必须创建一个不包含重复元素的集合(如上所定义)。

    注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响equals比较的方式更改,而对象是集合中的元素, 则不指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。

    一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerExceptionClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。 此异常在此接口的规范中标记为“可选”。

    Set接口特点:

    1、不允许存储重复的元素。

    2、没有索引,没有带索引的方法,也不能使用普通的for循环遍历。

    方法与collecion一致。

HashSet

  • public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, Serializable

    此类实现Set接口,由哈希表(实际为HashMap实例)支持对集合的迭代次序不作任何保证;特别是,它不能保证订单在一段时间内保持不变。这个类允许null元素。

    这个类提供了基本操作(add,remove,containssize)固定的时间性能,假定哈希函数将分散的桶中正确的元素。 迭代此集合需要与HashSet实例的大小(元素数量)和后台HashMap实例(桶数)的“容量”的总和成比例的时间。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。

    请注意,此实现不同步。 如果多个线程并发访问哈希集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该集合应该使用Collections.synchronizedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:

      Set s = Collections.synchronizedSet(new HashSet(...)); 

    该类iterator方法返回的迭代器是故障快速的 :如果集合在迭代器创建之后的任何时间被修改,除了通过迭代器自己的remove方法之外,迭代器会抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。

    请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。

    • booleanadd(E e) 将指定的元素添加到此集合(如果尚未存在)。
      void clear() 从此集合中删除所有元素。
      Object clone() 返回此 HashSet实例的浅层副本:元素本身不被克隆。
      boolean contains(Object o) 如果此集合包含指定的元素,则返回 true
      boolean isEmpty() 如果此集合不包含元素,则返回 true
      Iterator iterator() 返回此集合中元素的迭代器。
      boolean remove(Object o) 如果存在,则从该集合中删除指定的元素。
      int size() 返回此集合中的元素数(其基数)。
      Spliterator spliterator() 在此集合中的元素上创建late-binding故障快速 Spliterator

HashSet接口特点:

1、不允许存储重复的元素。

2、没有索引,没有带索引的方法,也不能使用普通的for循环遍历。

3、是一个无序的集合,存储元素和取出元素的顺序有可能不一致。

4、底层是一个哈希表结构(查询速度非常快)。

public class MyHashSet {
  public static void main(String[] args) {
      Set<Integer> set = new HashSet<>();
      set.add(1);
      set.add(2);
      set.add(3);
      set.add(1);//是否存储?
      Iterator<Integer> it = set.iterator();
      while (it.hasNext()){
          System.out.println(it.next());
      }
      System.out.println("---------------------");
      for (Integer integer : set) {
          System.out.println(integer);
      }
  }
}
运行结果:
1
2
3
---------------------
1
2
3

image-20200503004136424

image-20200503004519709

image-20200503005912646

HashSet存储自定义类型元素:重写hashcode和equals方法

public class Person {
  private String name;
  private Integer age;

  public Person() {
  }

  public Person(String name, Integer age) {
      this.name = name;
      this.age = age;
  }

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  public Integer getAge() {
      return age;
  }

  public void setAge(Integer age) {
      this.age = age;
  }

  @Override
  public boolean equals(Object o) {
      if (this == o) {
          return true;
      }
      if (o == null || getClass() != o.getClass()) {
          return false;
      }
      Person person = (Person) o;
      return Objects.equals(name, person.name) &&
              Objects.equals(age, person.age);
  }

  @Override
  public int hashCode() {
      return Objects.hash(name, age);
  }

  @Override
  public String toString() {
      return "Person{" +
              "name='" + name + '\'' +
              ", age=" + age +
              '}';
  }
}
//创建hashset集合存储person
HashSet<Person> hashSet = new HashSet<Person>();
Person p1 = new Person("haha", 18);
Person p2 = new Person("haha", 18);
Person p3 = new Person("haohoa", 19);
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
System.out.println(p1.equals(p2));
hashSet.add(p1);
hashSet.add(p2);
hashSet.add(p3);
for (Person person : hashSet) {
  System.out.println(person);
}

Iterator<Person> iterator = hashSet.iterator();
while (iterator.hasNext()){
  System.out.println(iterator.next());
}
运行结果:
99039841
99039841
true
Person{name='haha', age=18}
Person{name='haohoa', age=19}
Person{name='haha', age=18}
Person{name='haohoa', age=19}

LinkedHashSet

  • public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, Serializable

    底层是一个哈希表(数组 + 链表/红黑树)+ 链表;多了一条链表(记录元素的存储顺序),保证元素的有序性。

    哈希表和链表实现了Set接口,具有可预测的迭代次序。 这种实现不同于HashSet,它维持于所有条目的运行双向链表。 该链表定义了迭代排序,它是将元素插入集合(插入顺序的顺序 。 请注意,如果一个元件被重新插入到组插入顺序不受影响 。 (元件e重新插入一组s如果当s.contains(e)将返回true之前立即调用s.add(e)被调用。)

    此实现可以让客户从提供的指定,通常杂乱无章的排序HashSet ,而不会导致与其相关的成本增加TreeSet 。 它可以用于生成与原始文件具有相同顺序的集合的副本,而不管原始集的实现:

      void foo(Set s) {
            Set copy = new LinkedHashSet(s);
            ...
        }

    如果模块在输入上进行设置,复制它,并且稍后返回其顺序由该副本确定的结果,则此技术特别有用。(客户一般都喜欢以相同的顺序返回事情。)

    该类提供了所有可选的Set操作,并允许null元素。 像HashSet,它提供了基本操作(add,containsremove)稳定的性能,假定散列函数散桶中适当的元件。 性能可能略低于HashSet ,由于维护链表的额外费用,但有一个例外:LinkedHashSet的迭代需要与集合的大小比例的时间,无论其容量如何。 HashSet的迭代可能更昂贵,需要与其容量成比例的时间。

    链接哈希集具有影响其性能的两个参数: 初始容量负载因子 。 它们的定义精确到HashSet 。 但是请注意,该惩罚为初始容量选择非常高的值是该类比HashSet不太严重的,因为迭代次数对于这个类是由容量不受影响。

    请注意,此实现不同步。 如果多个线程同时访问链接的散列集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,则应该使用Collections.synchronizedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:

      Set s = Collections.synchronizedSet(new LinkedHashSet(...)); 

    该类iterator方法返回的迭代器是故障快速的 :如果在创建迭代器之后的任何时间对该集合进行了修改,除了通过迭代器自己的remove方法之外,迭代器将会抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。

    请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。

posted @ 2020-05-03 18:05  半颗桃核  阅读(134)  评论(0编辑  收藏  举报