Set的概念和特性以及常用方法
Set可以被认为是一个集合,集合内部是同类型的元素,他们之间没有先后顺序,但是不允许重复!!!
Set中常用的方法有以下几个:
返回值 | 方法 |
---|---|
boolean | add( ) 向集合中添加元素 |
无 | clear( ) 去掉集合中所有的元素 |
boolean | contains( ) 判断集合中是否包含某一个元素 |
boolean | isEmpty( ) 判断集合是否为空 |
Iterator | iterator( ) 主要用于递归集合,返回一个Iterator()对象 |
boolean | remove(E e) 从集合中去掉特定的对象 |
int | size( ) 返回集合的大小 |
在Java中Set是一个继承自Collection接口,它主要有以下两个实现类:
TreeSet
TreeSet是基于平衡二叉搜索树(红黑树)实现的Set,因为二叉搜索树中序遍历为有序的(不明白可以看前一篇博客:二叉搜索树的思想,以及增删查改的实现)会多出来这么几种方法:
返回值 | 方法 |
---|---|
E | first() 返回集合中序遍历第一个 |
E | last() 返回集合中序遍历最后一个 |
E | pollFirst() 删除集合中序遍历第一个并返回 |
E | pollLast() 删除集合中序遍历最后一个并返回 |
E | floor(E e) 返回比传入元素小的所有元素中最大的一个,包含相等(因为传入的元素可能不在集合中,也可能在集合中,这个方法若传入元素在集合中则返回传入元素,如不在则返回比传入元素小的所有元素中最大的一个) |
E | lower(E e) 返回比传入元素小的所有元素中最大的一个 |
E | ceiling(E e) 返回比传入元素大的所有元素中最小的一个(包含传入元素和上面floor()一起理解) |
E | higher(E e) 返回比传入元素大的所有元素中最小的一个(不包含传入元素) |
下面通过代码演示一下上述方法:
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
set.add(1);
set.add(9);
set.add(5);
set.add(3);
set.add(10);
set.add(2);
System.out.println("first " + set.first());
System.out.println("last " + set.last());
//比key小的里面最大的一个
System.out.println("lower " + set.lower(3));
//比key小的里面最大的一个(包括key)
System.out.println("floor " + set.floor(3));
//比key大的里面最小的(包括key)
System.out.println("ceiling " + set.ceiling(3));
//比key大的里面最小的
System.out.println("higher " + set.higher(3));
System.out.println("contains " + set.contains(3));
System.out.println("remove(4) " + set.remove(4));
System.out.println("remove(3) " + set.remove(3));
System.out.println("size " + set.size());
System.out.println("是否为空 " + set.isEmpty());
set.clear();
System.out.println("是否为空 " + set.isEmpty());
}
运行结果:
需要注意的是,因为在将元素插入搜索树的过程中会比较带插入元素和集合中元素大小,所以当TreeSet中要存入自己实现的类为元素时,该类必须实现Comparab接口或者在创建Set时传入一个该类的Comparator比较器,对于Comparable和Comparator不熟悉的同学建议去看这篇博客:Java中的Comparable和Comparator到底该怎么用看完就全明白了
HashSet
HashSet是基于哈希表实现的,因为哈希表内部元素是无序的,所以HashSet不像TreeSet,HashSet没有和元素顺序有关的方法,像上面提到的first(),last(),lower()等。
下面演示一下:
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(3);
set.add(9);
set.add(2);
set.add(5);
set.add(4);
set.add(10);
set.add(8);
System.out.println("remove(9) " + set.remove(9));
System.out.println("contains(8) " + set.contains(8));
System.out.println("isEmpty() " + set.isEmpty());
System.out.println("size() " + set.size());
set.clear();
System.out.println("isEmpty() " + set.isEmpty());
}
运行结果:
和TreeSet对元素要求不同,因为哈希表需要根据元素得到哈希值然后,根据这个哈希值去计算该元素在哈希表中的下标,所以当我们要在HashSet中存入自己实现的类为元素时,需要实现hashCode() 和 equals() 方法(为什么还要实现equals方法? 因为要保证相同的元素的哈希值相同,程序又不知道哪两个元素相同,所以需要实现equals方法)。
举个例子,下面是我自己实现的Person类,当我们需要创建一个 “Set< Person > set = new HashSet()”时:
import java.util.Objects;
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//实现equals()方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
//实现hashCode()方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Set两种实现类的比较
Set底层结构 | TreeSet | HashSet |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
插入/删除/查找 时间复杂度 | O(logN) | O(1) |
是否有序 | 关于Key有序 | 理论上无序 |
线程安全 | 不安全 | 不安全 |
插入/删除/查找 区别 | 按照红黑树的特性进行插入和删除 | 1.先计算Key哈希地址 2. 然后进行插入和删除 |
比较与重写 | TreeSet中的元素必须实现Comparable或者在构建Set时传入元素专属Comparator,否则抛出ClassCastException异常 | 自定义类型需要重写equals和hashCode方法 |
应用场景 | 需要Key有序的场景 | Key是否有序不关心,需要更高的时间性能 |