Set集合
Set集合保存的元素是没有顺序、且不能重复的。它有三个实现类:HashSet、TreeSet、EnumSet
1、HashSet
HashSet按照Hash算法来存储集合中的元素,因此具有良好的存取和查找性能
特点:
不保证元素的排列顺序
HashSet不是同步的,所以是线程不安全的
集合元素可以是null
存储的特性:
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法,等到该对象的hashCode值,根据这个值来决定该对象在HashSet中存储的位置。
如果两个元素通过equals()方法返回true,但它们hashCode()方法返回值不同,HashSet会把它们存储在不同的位置,一样可以添加成功。
HashSet判断两元素相等的标准:
两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相同
tips:
1、如果把一个对象放入HashSet中时,如果需要重写该对象对应类的equals()方法时,则也应该重写其hash Code()方法。
具体规则:当两个对象通过equals()方法比较返回true,则这两个对象的hashCode值也应该相同。
2、如果两个对象通过equals()方法比较返回true,但hashCode()方法false,则HashSet会把这两个对象保存在不同的位置,这两个对象都可以添加成功。
3、如果两个对象通过equals()方法比较返回false,但hashCode()方法true,则HashSet会把这两个对象保存在同一个位置,在这个位置通过链式结构来保存多个对象。
因为HashSet访问集合元素时也是根据元素的hashCode值来快速定位,因此当HashSet中有两个以上的元素具有相同的hashCode值,将会导致性能的下降。
4、当从HashSet中访问元素时,HashSet先计算该元素的hashCode值(即调用该对象的hashCode()方法的返回值),然后直接到该hashCode值对应的位置去取出该元素,这就是HashSet速度很快的原因。
2、LinkedHashSet
定义:
1、LinkedHashSet也是根据元素的hashCode值来决定元素存储位置
2、但它同时使用链表维护元素的次序,从而可以保证元素是按照插入顺序进行保存的。
即:当遍历LinkedHashSet时,LinkedHashSet会按照元素的添加顺序来访问集合里的元素。
3、也是一个Set,因此也不允许元素重复
性能:
因为LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时将有很好的性能,因为它是以链表来维护内部顺序的。
3、TreeSet类
TreeSet是SortSet接口的实现类,TreeSet可以保证集合元素处于排序状态。
TreeSet相比于HashSet多了如下几个额外的方法:
TreeSet类集合元素排序方式:
一般而言TreeSet是根据元素实际值的大小来进行排序的,TreeSet采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序
和定制排序
1、自然排序
① 原理:
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小,然后将集合元素按照升序进行排列。
② 要求:
1、因为compareTo(Object obj)
方法是Comparable
接口中定义的方法,所以如果要把一个对象添加到TreeSet时,则该对象的类必须实现Comparable
接口,否则TreeSet无法进行比较,从而会抛出异常ClassCastException
2、因为只有相同类的实例才会比较大小,这就要求向TreeSet中添加的应该是同一个类的对象
③判断元素是否相等:
TreeSet判断两个元素是否相等的唯一标准是:两个对象通compareTo(Object obj)
方法比较是否返回0,如果返回0,则TreeSet会认为它们相等,否则就认为它们不相等。
④ 注意点
1、当把一个对象放入TreeSet中时,如果重写了该对象的 equals() 方法时,也应该保证该方法和 compareTo(Object obj)方法有一致的返回结果。规则 equals() 返回true时,compareTo(Object obj) 返回0.
2、 如果向TreeSet中添加一个可变对象后,并且程序修改了该可变对象的实例变量,这将导致与其他对象的大小顺序发生了改变,但TreeSet不会再重新调整它们的顺序。甚至有可能导致TreeSet中两个对象通过compareTo(Object obj)比较后返回0。
如果一旦改变了TreeSet集合里可变元素的实例变量,当试图再删除该对象时,TreeSet也会从删除失败(甚至集合中原有的、实例变量没有被修改但与修改后元素相等的元素也无法删除)
2、定制排序
① 原理:
1、通过Comparator接口来实现,该接口中包含一个 compare(T o1,T o2
方法,该方法用于比较o1和o2的大小。
)
2、实现定制排序,需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由Comparator对象负责集合元素的逻辑排序。
代码示例:
/*自定义类M*/
class M
{
int age;
public M(int age)
{
this.age = age;
}
public String toString()
{
return "M[age:" + age + "]";
}
}
/*自定义的排序规则*/
public class comparatorM implements Comparator{
@Override
public int compare(o1 , o2) {
M m1 = (M)o1;
M m2 = (M)o2;
// 根据M对象的age属性来决定大小,age越大,M对象反而越小
return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
}
}
public class TreeSetTest4 {
public static void main(String[] args)
{
// 提供一个Comparator对象与该TreeSet集合关联
TreeSet ts = new TreeSet(new comparatorM());
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(9));
System.out.println(ts);
}
}
②定制排序判断元素是否相等:
通过Comparator比较两个元素是否返回0,如果返回了0,则TreeSet是不会把第二个元素添加到集合当中。
4、EnumSet类
定义:
1、EnumSet是专门为枚举类型设计的集合类,其中所有的元素都必须是制定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。
代码示例:
/*枚举类型*/
enum Season
{
SPRING,SUMMER,FALL,WINTER
}
/*测试函数*/
public class EnumSetTest
{
public static void main(String[] args)
{
// 创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1); // 输出[SPRING,SUMMER,FALL,WINTER]
// 创建一个EnumSet空集合,指定其集合元素是Season类的枚举值。
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2); // 输出[]
// 手动添加两个元素
es2.add(Season.WINTER);
es2.add(Season.SPRING);
System.out.println(es2); // 输出[SPRING,WINTER]
// 以指定枚举值创建EnumSet集合
EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER);
System.out.println(es3); // 输出[SUMMER,WINTER]
EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER);
System.out.println(es4); // 输出[SUMMER,FALL,WINTER]
// 新创建的EnumSet集合的元素和es4集合的元素有相同类型,
// es5的集合元素 + es4集合元素 = Season枚举类的全部枚举值
EnumSet es5 = EnumSet.complementOf(es4);
System.out.println(es5); // 输出[SPRING]
}
}
2、EnumSet集合元素也是有顺序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序
3、EnumSet在内部以位向量的形式存储,该存储形式非常紧凑、高效,因此EnumSet对象占用内存小,且运行效率很好,尤其是进行批量操作(如:containsAll()和retainAll()方法时)
4、EnumSet不允许加入null元素,如果试图插入,会报空指针异常
5、当复制Collection集合中所有的元素来创建新的EnumSet集合时,要求Collection集合中所有的元素必须是同一个枚举类型的枚举值。
5、各Set实现类的性能分析
1、HashSet的性能总是好于TreeSet(特别是常用的添加、查找等操作),因为TreeSet需要额外的红黑树算法来维持集合元素的顺序,因此当需要一个保持排序的Set时,才应该使用TreeSet,否则使用HashSet
2、对于普通的插入、删除操作,LinkedHashSet要比HashSet慢一点,因为是由维护链表所造成的,但遍历时LinkedHashSet会快。
3、EnumSet是实现类中性能最好的,但它因为只能保存一个枚举类型当中的枚举值作为集合元素,所以有局限性
4、Set的三个实现类都是线程不安全的,为了保证线程安全,必须手动保证该Set集合的同步性,通常是通过Collections工具类的synchronizedSortedSet方法来“包装”该Set集合,此操作最好在创建时进行,以防止对Set集合的意外非同步操作,如:
SortedSet s = Collections.synchronizedSortedSet(new TreeSet<>());