Set集合
Set接口是 Collection 的子接口。
Set集合不允许包含相同的元素。
如果添加相同的元素, add()返回返回FALSE, 新元素不会加入。
public interface Set<E> extends Collection<E> {
// Query Operations
}
HashSet, TreeSet, EnumSet 是 Set 集合的三个实现类。
HashSet类
HashSet按照Hash算法存储集合中的元素。
HashSet有以下特点:
- 不能保证元素的排列顺序
- HashSet不是同步的,多个线程访问时,如果有多线程同时修改,需要代码来保证同步。
- 集合元素值可以是null
向HashSet 添加元素时,会调用该对象的 hashCode()得到该对象的hashCode值,根据hashCode值决定 HashSet的存储位置。
判断两个元素相等的标准是 通过 equals()方法比较相等,并且hashCode()返回的值也相等。
class A{
@Override
public boolean equals(Object obj) {
return true;
}
}
class B{
@Override
public int hashCode() {
return 1;
}
}
class C{
@Override
public int hashCode() {
return 2;
}
@Override
public boolean equals(Object obj) {
return true;
}
}
public class HashSetTest {
public static void main(String[] args){
HashSet books = new HashSet();
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);
}
}
//Output
[testCode.B@1, testCode.B@1, testCode.C@2, testCode.A@7440e464, testCode.A@49476842]
上面例子发现,C只添加了一个, A, B都加入了2个对象。
如果两个对象 hashCode相同, equals为FALSE, HashSet会试图存储在一个位置,这个位置会用链式结构来保存多个对象。
重写equals()和hashCode()时,应尽量保证equals()为TRUE时,hashCode()也相等。
LinkedHashSet类
LinkedHashSet是 HashSet的子类
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
}
LinkedHashSet根据元素的hashCode值决定元素的存储位置,同时使用链表维护元素的次序。使元素看起来是按照插入顺序保存的。
由于要维护插入顺序,性能略低于HashSet性能,但迭代访问时有很好的性能。
LinkedHashSet books1 = new LinkedHashSet();
books1.add("A");
books1.add("B");
books1.add("C");
System.out.println(books1);
books1.remove("A");
books1.add("A");
System.out.println(books1);
//Output
[A, B, C]
[B, C, A]
元素顺序与添加时一致。
TreeSet
SortedSet 是 Set的子接口。
TreeSet是 SortedSet接口的实现类,保证集合元素处于排序状态。
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
}
public interface NavigableSet<E> extends SortedSet<E> {
}
public interface SortedSet<E> extends Set<E> {
}
TreeSet增加了以下方法:
comparator(): 返回定制排序的Comparator;
first(): 返回第一个元素
last(): 最后一个元素
lower(): 指定元素之前的元素
higher(): 指定元素之后的元素
subSet():返回子集
headSet(): 小于指定元素的子集
tailSet():大于等于指定元素的子集
TreeSet采用红黑树的数据结构来存储集合元素。
TreeSet nums = new TreeSet();
nums.add(2);
nums.add(-5);
nums.add(7);
System.out.println(nums);
System.out.println(nums.last());
System.out.println(nums.tailSet(2));
//Output
[-5, 2, 7]
7
[2, 7]
TreeSet支持两种排序规则: 自然排序和定制排序。
自然排序
TreeSet会调用集合元素的 compareTo()方法比较元素的大小关系,然后按照升序排列, 这种为自然排序。
Java提供 Comparable 接口, 定义了 compareTo()方法,实现该接口的类必须实现该方法。
String, 包装类,Boolean, Date, Time 等都实现了Comparable接口。
当向TreeSet添加元素时,第一个元素无须实现Comparable接口, 后面添加必须实现 Comparable 接口
class D{}
public class HashSetTest {
public static void main(String[] args){
TreeSet ts = new TreeSet();
ts.add(new D());
ts.add(new D());
}
}
java.lang.ClassCastException: testCode.D cannot be cast to java.lang.Comparable.
上面代码会抛出这个异常。
TreeSet应该添加同一种类型的对象。
否则也会报 ClassCastException。
因为大部分类实现的 compareTo()方法时,比较对象会强制转换为相同的类型。
当向TreeSet中添加对象时, TreeSet调用 compareTo()与容器中其他对象比较大小,根据红黑树找到它的存储位置,如果compareTo()比较相等,则不能添加到集合中。
重写类的equals()方法时,应该保证和compareTO()有一致的结果。
定制排序
定制排序需要创建TreeSet时,提供一个 Comparator对象, 实现排序逻辑。
Comparator 是函数式接口,可使用Lambda表达式。
class E{
int age;
public E(int age){
this.age = age;
}
@Override
public String toString() {
return "E [age]:" + age;
}
}
public class HashSetTest {
public static void main(String[] args){
TreeSet treeSet1 = new TreeSet(((o1, o2) -> {
E e1 = (E) o1;
E e2 = (E) o2;
return e1.age > e2.age ? -1 : e1.age < e2.age ? 1: 0;
}));
treeSet1.add(new E(5));
treeSet1.add(new E(-2));
treeSet1.add(new E(8));
System.out.println(treeSet1);
}
}
//Output
[E [age]:8, E [age]:5, E [age]:-2]
EnumSet
EnumSet是专门给枚举类设计的集合类。
所有元素必须是枚举类型的枚举值
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable
{
}
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
}
EnumSet内部以位向量的方式存储。
不允许加入 null元素。
EnumSet 没有暴露任何构造器来创建类实例。
可以使用下面方法创建EnumSet对象。
- allOf(): 包含全部枚举值的集合
- complementOf(): 不包含指定集合元素,剩余部分新建一个集合
- copyOf(): 使用普通集合创建
- of():包含一个或多个枚举值的集合
- range(): 根据范围创建集合。
enum Season{
SPRING,SUMMER,FALL,WINTER;
}
public class HashSetTest {
public static void main(String[] args){
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1);
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2);
es2.add(Season.FALL);
es2.add(Season.SPRING);
System.out.println(es2);
}
}
//Output
[SPRING, SUMMER, FALL, WINTER]
[]
[SPRING, FALL]
创建时要求同一个集合元素是同一个枚举类的枚举值。
各个Set性能分析
HashSet 的性能好于 TreeSet, 因为 TreeSet使用红黑树维护顺序。
LinkedHashSet比 HashSet略慢一点, 因为维护链表带来了额外开销。但是遍历会更快。
EnumSet是所有Set实现类中性能最好的。
HashSet,TreeSet,EnumSet都是线程不安全的,
多线程手动同步,可以使用 Collections 工具类包装。
SortedSet sortedSet = Collections.synchronizedSortedSet(new TreeSet<>());