Java集合之Set

Java集合之Set

前面介绍过Java集合大致分为三个:Set、List和Map,Java 5之后还多了一个Queue,而Set和List以及Queue的父接口则是Collection接口。现在我们来讲一些Set。

Set也是一个接口,Set是一个无序、不可重复的集合。实际上Set就是Collection,只是Set不可重复而已。所以当Set添加相同的对象进去的时候Set的add方法会返回false。

Set里面是怎么判断两个对象是否是相等的呢?Set判断两个对象是否相等不是通过==运算符来判断的,是通过对象的equals方法来判断的。所以当equals方法返回为true时,Set就会把它们当做同一个对象来对待。随意当重写一个类的equals方法时,如果直接返回true,那么Set就会把该类的所有对象都当成同一个对象;或者经过一系列运算后返回为true,那么Set就会把这两个对象当成同一个对象。如下代码:

Set bookNames = new HashSet();
booksNames.add(new String("Java编程思想"));
boolean result = booksNames.add(new String("Java编程思想"));
System.out.println(result);

add方法里面都是通过new的对象,所以这两个对象肯定不是同一个对象,但是从打印的结果可以看到result的结果为false,所以Set是没有添加进第二个String的。那是因为String的equals方法比较的是字符串是否相等,String的内容都是Java编程思想,所以Set把它们都当成一个对象了。
这个是Set的通用规则,所以所有实现了Set接口的类都遵循这个原则,所以也完全适用于接下来讲的HashSet、LinkedHashSet、TreeSet、EnumSet。

HashSet

当我们使用Set的时候大部分的时候都是使用HashSet这个实现类的。HashSet是按照Hash算法来存储集合中的元素的,所以HashSet有很好的存取和查找性能。

因为HashSet是根据Hash算法来存储数据的,那么就肯定会用到对象的hashCode值,也就是会调用hashCode方法。所以当要把一个对象存储到HashSet集合中时,对象不同的那么hashCode值必须不同。

如果两个对象通过equals方法比较返回true时,那么它们的hashCode方法返回的值也必须相等。如果equals返回的是true,但是hashCode值不一样,那么HashSet就会把它们存放到不同的位置,这就跟Set集合的规则不一样了。

如果quals方法返回的是false,而hashCode值相同,那么HashSet就会把这几个hashCode值相同的对象存储到同一个地方,那么这个位置就会出现多个对象。这个时候这个位置会用链式结构来保存多个对象,而当访问HashSet时则会导致性能下降,因为HashSet是根据hashCode值来查找的。

综上所述:HashSet的存储规则是:通过equals方法和hashCode方法两个方法同时判断对象是否是同一个对象。当equals方法返回为true时,hashCode必须相等。

重写hashCode方法时需要重新计算hashCode值,计算方法大概是这样的:把对象的每个有意义的Field(即每个用做equals方法比较标准的Field)计算出一个int类型的hashCode值(这个计算方法自己Google,具体怎么计算我也忘记了),然后通过计算出的多个值组合计算出一个hashCode值,这个hashCode值就是重写hashCode方法时返回的值。

LinkedHashSet

LinkedHashSet是HashSet的一个子类,LinkedHashSet也是通过hashCode来存储的,只是LinkedHashSet还会用一个链表来维护元素的次序,这样就会使得元素的存储是有序的(实际还是无序的)。当遍历LinkedHashSet时,LinkedHashSet会按照元素的添加顺序来访问。因为要维护元素的插入顺序,所以性能会比HashCode的性能略低,但是当需要迭代Set里面的全部元素时会有更好的性能。

TreeSet

TreeSet是SortedSet接口的实现类,从SortedSet的名字我们就可以知道这个是有排序的一个接口。TreeSet是通过红黑树来存储位置的,而排序规则则分为两种:自然排序和定制排序。

自然排序

TreeSet通过调用compareTo(Object o)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这就是自然排列。

Java提供的Comparable接口,该接口定义了一个compareTo(Object o)方法,实现该接口的类必须实现该方法。比如:obj1.compareTo(obj2)。当该方法返回一个正数时,表示obj1大于obj2,当该方法返回负值时,表示obj1小于obj2,等于0则说明两个对象相等。

当使用compareTo(Object o)比较两个对象时,两个对象必须都实现了Comparable接口,如果没有实现Comparable接口,那么会抛出ClassCastException异常。同时,通过compareTo(Object o)方法比较两个对象时,这两个对象必须是同一种类型,或者能够强制转换成同一种类型。所以当往TreeSet添加元素时,添加的元素必须要同一种类型。如果添加两种不同类型的对象到TreeSet里面,则会抛出ClassCastException异常。

TreeSet添加元素时是先通过compareTo(Object o)方法比较两个元素的大小,然后根据红黑树结构找到存储它的位置,如果比较的结果是相等的,那么就不会添加到TreeSet里面。

定制排序

当想要TreeSet以其他的顺序排序时,则需要通过Comparable接口来实现。在创建TreeSet对象时,提供一个Comparable对象与该TreeSet集合关联,由该Comparable对象负责集合元素的集合排序逻辑。例如:

TreeSet ts = new TreeSet(new Comparable(){
    public int compare(Object obj1, Object obj2){
        M m1 = (M)obj1;
        M m2 = (M)obj2;
        return m1 > m2 ? -1 : m1 < m2 ? 1 : 0;
    }
});

EnumSet

EnumSet是一个专门为枚举类设计的集合类,EnumSet中的所有元素必须是指定枚举类型的枚举值。

EnumSet内部是以位向量的形式存储。这种存储形式非常的高效、紧凑,因此EnumSet赵勇的内存很小,而且运行效率很高,尤其是进行批量操作时。
EnumSet不能加入null值,如果试图插入null值,则会抛出NullPointException异常。
EnumSet没有暴露出任何构造方法来创建对象的实例,所以EnumSet里面的方法都是static方法。

各Set实现类的性能比较

EnumSet是所有Set实现类中性能最好的,但它只能保存枚举类型。

HashSet的性能总是比TreeSet要好,特别是最常用的添加、查询操作,因为TreeSet需要通过红黑树来维护元素的顺序。

LinkedHashSet对于普通的插入、删除操作,性能会比HashSet要略慢一些,这是由维护链表结构造成的,但是如果是遍历操作,那么会比HashSet快。

最后再说一下:Set的三个实现类:HashSet、TreeSet、EnumSet都是线程不安全的。所以如果有多个线程同时访问一个Set集合,并且有超过一个线程修改了Set集合,那么必须手动保证该Set集合的同步性,通常可以通过Collections工具类的synchronizedSortedSet方法来包装该Set集合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值