Java-集合第二篇Set集合
1、Set集合与Collection基本相同,没有提供额外的方法。实际上Set就是Collection,只是行为略有所不同(Set不允许有重复元素)。
Set下的HashSet、TreeSet、EnumSet完全适用于上面Set的有关规则(即元素不能够重复)。
2、HashSet
(1)HashSet按Hash算法来存储集合中的元素,因此具有良好的存取和查找性能。
HashSet具有的特点:
1》不能保证元素的排列顺序。
2》HashSet不是同步的,即不是线程安全的。
3》集合元素值可以为null。
(2)当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。
HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
1》如果有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet就认为它们是不同的元素,依然可以添加到HashSet中,它们将存储在不同的位置。(注:public boolean equals(Object o)、public int hashCode()方法均来自Object)
所以,当某种类型需要放到HashSet中,并且需要重写其equals()方法,则相应的hashCode()方法也应该重写。规则是:如果两个对象通过equals()方法比较返回返回true,这两个对象的hashCode值也应该相同。
2》如果两个元素比较equals()方法返回true,hashCode()返回不同的值,这与Set集合的规则冲突;equals()返回false,hashCode()返回相同的值,HashSet将试图把它们保存在同一个位置,但又不行,这时候在这个位置使用链式结构来保存多个对象。
HashSet访问元素是根据元素的hashCode值来快速定位的,如果HashSet中存在两个以上的元素具有相同的hashCode值(上面使用链式结构保存),将会导致性能下降。
3》HashSet中每个能存储元素的“槽位”(slot)通常也称为“桶”(bucket),如果多个元素equals()返回false,hashCode()返回相同hashCode(链式结构存储),就需要在“桶”中放多个元素,从而导致性能下降。重写hashCode()方法的基本规则:
1>同一个对象调用多次hashCode()方法应该返回相同的值。
2>当equals()方法返回true时,hashCode()应该返回相同的hashCode值。
3>对象中用作equals()方法比较标准的实例变量,都应该用于计算hashCode值。
(3)重写hashCode()方法的一般步骤
1》把对象内每个有意义的实例变量(即每个参与equals()方法比较标准的实例变量)计算出一个int类型的hashCode值。计算方式举例:
实例变量类型 | 计算方式 |
boolean | hashCode=(f?0:1); |
float | hashCode=Float.floatToIntBits(f); |
double |
long l=Double.doubleToLongBits(f); hashCode=(int)(l^(1>>>32)); |
long | hashCode=(int)(f^(f>>>32)); |
整数类型(byte、short、char、int) | hashCode=(int)f; |
引用类型 | hashCode=f.hashCode(); |
2》利用第一步计算得到的多个hashCode值组合计算出一个hashCode值返回,如直接相加作为最终的hashCode值返回。
3》为了避免直接相加产生的偶然相等,可以通过为各实例变量的hashCode值乘以任意一个质数后再相加。
return f1.hashCode()*17+(int)f2*29;
(4)如果向HashSet中添加了一个可变对象后,后面程序修改了该可变对象的实例变量值,则可能导致它与集合中的其他元素相同(equals()返回true,hashCode()也返回相同的值),从而可能导致HashSet中包含两个相同的对象。这是一个值得注意的问题。
3、LinkedHashSet
也是通过元素的hashCode值决定元素的存储位置,但它同时使用链表维护元素的次序,遍历LinkedHashSet中的元素时,LinkedHashSet将会按照元素添加顺序来访问集合中的元素。
LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问集合中的全部元素时将有很好的性能。
4、TreeSet
(1)TreeSet是SortedSet接口的实现实现类,TreeSet可以确保集合元素处于排序状态。与HashSet集合相比,TreeSet还提供了一下的额外方法:
1》Comparator comparator():如果TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator;如果TreeSet采用了自然排序,则返回null。
2》Object first():返回集合中的第一个元素。
3》Object last():返回集合中的最后一个元素。
4》Object lower(Object o):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要是TreeSet集合里的元素)。
5》Object higher(Object o):返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素不需要是TreeSet集合里的元素)。
6》SortedSet subSet(Object fromElement,Object toElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)。
7》SortedSet headSet(Object toElement):返回此Set的子集,由小于toElement的元素组成。
8》SortedSet tailSet(Object fromElement):返回此Set的子集,由大于或者等于fromElement的元素组成。
Tips:TreeSet是有序的,所以上面的方法为其提供访问第一个、前一个、后一个、最后一个元素,还有3个从TreeSet中截取子元素的方法。
TreeSet采用红黑树来决定元素的存储位置。TreeSet支持两种排序方式:自然排序和定制排序,默认采用自然排序。
(2)自然排序
TreeSet会调用集合元素的comparaTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。当一个对象调用该方法与另一个对象进行比较时,如obj1.compareTo(obj2),返回0,则两个对象相等;返回正整数,则obj1大于obj2;返回负数,则obj1小于obj2。
Java实现了Comparable接口的常用类:
1》BigDecimal、BigInteger以及所有的数值型对应的包装类:按它们对应的数值大小进行比较。
2》Character:按字符的UNICODE值进行比较。
3》Boolean:true对应的包装类实例大于false对应的包装类实例。
4》String:按字符串中字符的UNICODE值进行比较。
5》Date、Time:后面的时间、日期比前面的时间、日期大。
如果试图将一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常(常见的强制类型转换错误)。注意,向TreeSet集合添加元素时,只有第一个元素无需实现Comparable接口,后面添加的所有元素都必须实现Comparable接口,这种时候能够成功添加,但当取元素的时候,还是会引发强制类型转换错误。
所以,一般TreeSet中只能添加同一种类型的对象。
当把一个对象加入TreeSet集合中时,TreeSet调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置。如果两个对象通过compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中。
TreeSet判断两个元素是否相等的唯一标准是compareTo(Object obj)是否返回0,不返回0就是不相等。
class Z implements Comparable{ int age;
public Z(int age){
this.age=age;
}
@Override
public boolean equals(Object obj){
return true;
}
@Override
public int compareTo(Object obj){
return 1;
}
} public class TreeSetTest{
TreeSet set=new TreeSet();
Z z1=new Z(6);
set.add(z1);
set.add(z1);
//第二次添加同一个对象,输出true,表名添加成功
System.out.println(set.add(z1));
//输出set集合,看到两个元素
System.out.println(set);
//修改第一个元素的age
((Z)(set.first())).age=9;
//输出set,可以看到两个元素一样了!!!
System.out.println((set);
}
从上面结果,由于equals()返回true,但compareTo()返回一个正整数,导致第一个元素和第二个元素是同一个元素,只是它们在集合中存储的是不同的引用值。
当需要把一个对象放入TreeSet中,重写该对象对应类的equals()方法时,应该保证该方法与compareTo(Object obj)方法有一致的结果。其规则是:如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较应该返回0。
如果compareTo()返回0,equals()返回false,TreeSet不会让第二个元素添加进集合,这与Set集合的规则冲突(非重复元素添加失败)。
往TreeSet中添加可变对象,可能引发两个对象compareTo()返回0。
(3)定制排序
TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排序。如果需要定制排序,如降序排序,则可以通过Comparator接口的帮助。该接口包含一个int compare(T o1,T o2)方法,该方法用于比较o1和o2的大小:返回正整数,则o1大于o2;返回0,则o1等于o2;返回负数,则o1小于o2。
如果需要实现定制排序,则需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。Comparator是一个函数式接口,因此可以使用Lambda表达式来替代Comparator对象。
5、EnumSet
专门为枚举类设计的集合类。EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。
EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好。
EnumSet集合不允许加入null元素,否则将抛出空指针异常。
EnumSet类没有暴露任何构造器来创建该类的实例,但它提供了类方法创建对象。提供创建EnumSet对象的方法有:
1》EnumSet allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的EnumSet集合。
2》EnumSet complement(EnumSet s):创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此枚举类剩下的枚举值。
3》EnumSet copyOf(Collection c):使用一个普通集合来创建EnumSet集合。
4》EnumSet copyOf(EnumSet e):创建一个与指定EnumSet具有相同元素类型、相同集合元素的EnumSet集合。
5》EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型的空EnumSet。
6》EnumSet of(Efirst,E...rest):创建一个包含一个或者多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举值。
7》EnumSet range(E from,E to):创建一个包含从from枚举值到to枚举值范围内所有枚举值的EnumSet集合。
6、各Set实现类的性能分析
HashSet的性能总是比TreeSet好(添加、查询元素),因为TreeSet需要额外的红黑树算法维护集合元素的次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
HashSet的子类LinkedHashSet,对于普通的插入、删除操作,LinkedHashSet比HashSet要略微慢一点,这是由维护链表所带来的的额外开销造成的,但后期遍历会变得很快。
EnumSet是所有Set实现类这种性能最好的,但局限于只能保存某一枚举类的枚举值作为集合元素。
Set的3个实现类HashSet、LinkedHashSet、EnumSet,都不是线程安全的,通常可以通过Collections工具类的synchronizedSortedSet方法来“包装”该Set集合,此操作最好在创建时进行,以防止对Set集合的意外非同步访问。
SortedSet s=Collections.synchronizedSortedSet(new TreeSet(...));
Java-集合第二篇Set集合的更多相关文章
- Java SE 第二篇
二. Java SE 第二篇 1. Arrays 数组 // 声明一维数组,[]内不允许有值 int[] arr; int arr[]; // 创建一维数组对象,[]内必须有值 arr = new ...
- 8成以上的java线程状态图都画错了,看看这个-图解java并发第二篇
本文作为图解java并发编程的第二篇,前一篇访问地址如下所示: 图解进程线程.互斥锁与信号量-看完还不懂你来打我 图形说明 在开始想写这篇文章之前,我去网上搜索了很多关于线程状态转换的图,我惊讶的发现 ...
- 从.Net到Java学习第二篇——IDEA and start spring boot
从.Net到Java学习第一篇——开篇 所谓工欲善其事,必先利其器,做java开发也一样,在比较了目前最流行的几个java IDE(eclipse,myeclipse.IDEA)之后,我果断选择IDE ...
- SVN第二篇-----命令集合
16.switch 代码库URL变更 svn switch (sw): 更新工作副本至不同的URL. 用法: 1.switch URL [PATH] 更新你的工作副本,映射到一个新 ...
- java基础第二篇
3.选择结构 a.if: 格式一: if(表达式1){ 表达式1为真才执行 } 格式二: if(表达式1){ 表达式1为真才执行 }else{ 表达式1位假才执行 } 格式三:判断工龄的范围,判断成绩 ...
- Java高新技术第二篇:反射技术
今天我们来看一下Java中的反射技术: 首先来了解一下Java中的反射的一些概念: Java中的反射是1.2引入的 反射的基石:class类 Class类的各个实例对象分别对应各个类在内存中的字节码, ...
- 【Bootstrap】Bootstrap和Java分页-第二篇
目录 关于此文 配置xml-pager.tld 分页控件-Pager 分页action集成类-BaseController 实例-Dao 实例-service 实例-action 实例-JSP 实例- ...
- Java 学习 第二篇;面向对象 定义类的简单语法:
1:基本知识 [public / protected / private] class 类名 { 零个到多个构造器定义; 零个到多个属性; 零个到多个方法; } 其中类中各个成员之间的顺序没有关系,且 ...
- 聊聊、Java 命令 第二篇
第一篇类之间没有依赖关系,所以比较简单,这一篇来看看有依赖的类怎么编译和执行. (一)Java 运行 class 有依赖 Person 是一个接口,只有一个 getName 方法.Man 则实现了 P ...
随机推荐
- GUI学习之十六——QSpinBox学习总结
我们在上一章讲了步长调节器QAbstractSpinBox,这一节来讲一下它的一个子类:QSpinBox 一.描述 QSpinBox是一个主要处理整数和离散值集合的步长调节器控件,它允许用户通过单击增 ...
- 动态SQL的注意
MyBatis的动态SQL元素. 元素 说明 <if> 判断语句,用于单条件分支判断 <choose>(<when>.<otherwise>) 相当于j ...
- Comet OJ - 模拟赛 #2 Day1 比赛总结
比赛情况 40 + 60 + 0 = 100pts 哎,T1做错了,没有对拍.如果发现错误 \(=>\) 改正 \(=>\) 40->100pts,160pts \(=>\) ...
- 开发过程中git的使用
使用clone命令可以直接将git添加到本地库: 主要是针对分支的操作: 首先可以将创建一个属于自己的分支并往上面提交代码,最后合并到dev分支和master分支上面: 前提(master已经有一个文 ...
- java: Set类及子类:TreeSet有序子类,HashSet无序子类:重复元素
Set类及子类: TreeSet有序子类: HashSet无序(散列)子类 HashSet子类的内容是没有顺序的,单个元素也不会重复的(对象除外). Set<String> allSet ...
- Top 8 Diagrams for Understanding Java
Reference: http://www.programcreek.com/2013/09/top-8-diagrams-for-understanding-java/ A diagram is s ...
- java调用存储过程的方式
1.问号是入参和出参,出参要指定类型 CallableStatement pstmt = conn.prepareCall("{call dbo.UP_CodeUp_***(?,?,?,?, ...
- c++ string转char*
1.如果要将string转换为char*,可以使用string提供的函数c_str() ,或是函数data(),data除了返回字符串内容外,不附加结束符'\0',而c_str()返回一个以‘\0’结 ...
- Java——容器(Auto-boxing/unboxing)
[打包/解包] 在Map中需要增加一个数值时,需要new一个对象出来,输出后又得进行强制类型转换,这就造成不便.在JDK1.5中使用Map接口提供了一种新的机制. 在合适的时机自动打包/解包(在J ...
- 转载--C 的回归
转载自http://blog.codingnow.com/2007/09/c_vs_cplusplus.html 周末出差,去另一个城市给公司的一个项目解决点问题.回程去机场的路上,我用手机上 goo ...