JavaSE(八)集合之Set
今天这一篇把之前没有搞懂的TreeSet中的比较搞得非常的清楚,也懂得了它的底层实现。希望博友提意见!
一、Set接口
1.1、Set集合概述
Set集合:它类似于一个罐子,程序可以依次把多个对象 “丢进” Set 集合,而 Set 集合通常不能记住元素的添加的顺序,也就是说Set 集合是无序的。
Set 集合与 Colleaction 基本相同,没有提供额外的方法,实际上 Set 就是 Collection,只是行为略有不同(Set 不允许包含重复元素)。
1.2、Set类型集合特点
集合中的元素不可重复,无索引,有没有序要看Set接口具体的实现类是谁。
Set接口常见的实现类有:HashSet、LinkedHashSet
HashSet集合中元素的特点 :无序不可重复
LinkedHashSet集合中元素的特点:有序不可重复
二、HashSet
2.1、HashSet概述
Set 接口的典型实现类,HashSet是基于HashMap实现,大多数时候使用 Set 集合时就是使用这个实现类。HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。数据结构是哈希表。线程是非同步的。
2.2、HashSet特点
不能保证元素的排列顺序,顺序可能和添加的顺序不同,顺序也有可能发生变化。
HashSetf不是同步的,如果多个线程同时来访问一个 HashSet,假设有两个或者两个以上线程同时修改了HashSet 集合时,则必须通过代码来保证其同步。
集合元素值可以是 null。
2.3、HashSet如何保证元素唯一性的原理
当我们想要创建一个集合,该集合里面的元素都具有唯一性时。会遇到两种情况:
1)元素为String类型,可以直接用Hashset<String>集合来创建
2)String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉,只留下一个。
思路图:
3)HashSet保证元素唯一性的原理
我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数
当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象
如果没有哈希值相同的对象就直接存入集合
如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存。
4)将自定义类的对象存入HashSet去重复
类中必须重写hashCode()和equals()方法
hashCode(): 属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)
equals(): 属性相同返回true, 属性不同返回false,返回false的时候存储。
在开发中并不要我们去写,比如使用eclipse开发中,在类上面 Alt+Shift+s ,再点h,就能生成相对应的重写的hashCode()和equls()方法了。那我们就来分析一下,该怎么写的。
/*
注意:这里是一个Student类:里面有name和age属性。
* 为什么是31?
* 1,31是一个质数,质数是能被1和自己本身整除的数
* 2,31这个数既不大也不小
* 3,31这个数好算,2的五次方-1,2向左移动5位
*/
@Override
public int hashCode() {
final int prime = ;
int result = ;
result = prime * result + age;
result = prime * result + ((name == null) ? : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) //调用的对象和传入的对象是同一个对象
return true; //直接返回true
if (obj == null) //传入的对象为null
return false; //返回false
if (getClass() != obj.getClass()) //判断两个对象对应的字节码文件是否是同一个字节码
return false; //如果不是直接返回false
Person other = (Person) obj; //向下转型
if (age != other.age) //调用对象的年龄不等于传入对象的年龄
return false; //返回false
if (name == null) { //调用对象的姓名为null
if (other.name != null) //传入对象的姓名不为null
return false; //返回false
} else if (!name.equals(other.name)) //调用对象的姓名不等于传入对象的姓名
return false; //返回false
return true; //返回true
}
细说hashCode和equals方法
2.4、一个案例来说明问题
package com.zyh.domain; public class Person {
private String name;
private int age; public Person(String name,int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} /*
* 为什么是31?
* 1,31是一个质数,质数是能被1和自己本身整除的数
* 2,31这个数既不大也不小
* 3,31这个数好算,2的五次方-1,2向左移动5位
*/
@Override
public int hashCode() {
final int prime = ;
int result = ;
result = prime * result + age;
result = prime * result + ((name == null) ? : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) //调用的对象和传入的对象是同一个对象
return true; //直接返回true
if (obj == null) //传入的对象为null
return false; //返回false
if (getClass() != obj.getClass()) //判断两个对象对应的字节码文件是否是同一个字节码
return false; //如果不是直接返回false
Person other = (Person) obj; //向下转型
if (age != other.age) //调用对象的年龄不等于传入对象的年龄
return false; //返回false
if (name == null) { //调用对象的姓名为null
if (other.name != null) //传入对象的姓名不为null
return false; //返回false
} else if (!name.equals(other.name)) //调用对象的姓名不等于传入对象的姓名
return false; //返回false
return true; //返回true
} @Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Person
package com.zyh.Collection.set; import com.zyh.domain.Person; import java.util.HashSet; public class HashSetDemo_0010 {
/*
HashSet集合存储自定义对象并遍历。如果对象的成员变量值相同即为同一个对象 注意了:
你使用的是HashSet集合,这个集合的底层是哈希表结构。
而哈希表结构底层依赖:hashCode()和equals()方法。
如果你认为对象的成员变量值相同即为同一个对象的话,你就应该重写这两个方法。
如何重写呢?不同担心,自动生成即可。
*/
public static void main(String[] args) {
HashSet<Person> hs = new HashSet<>();
hs.add(new Person("boy",));
hs.add(new Person("girl",));
hs.add(new Person("girl",));
hs.add(new Person("boy",));
hs.add(new Person("person",));
hs.add(new Person("boy",));
hs.add(new Person("person",));
hs.add(new Person("boy",));
hs.add(new Person("girl",)); //遍历集合
for(Person p : hs){
System.out.println(p.getName()+":"+p.getAge());
}
}
}
HashSetDemo_0010
三、LinkedHashSet
3.1、LinkedHashSet概述
1)LinkedHashSet是HashSet的子类,LinkedHashSet 集合也是根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。
也就是说,当遍历 LinkedHashSet 将会按照添加元素顺序来访问集合里的元素。
2)LinkedHashSet是通过用一个链表的实现来扩展HashSet,从而支持了对HashSet中的元素的排序。所以LinkedHashSet可以按照元素插入时的顺序进行提取。
3)LinkedHashSet 需要维护元素的插入顺序,因此性能略低于 HashSet 的性能,但在迭代访问 Set 里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
虽然 LinkedHashSet 使用了链表记录集合元素的添加顺序,但 LinkedhasHSet依然是 HashSet,因此它依然不允许集合元素重复。
4)LinkedHashSet只能按照先后顺序来进行排序,TreeSet则是按照比较器给的比较规则进行从小到大排序。其实现也就是借助于TreeMap。
3.2、LinkedHashSet的特点
一是:保证元素唯一。二是:可以保证怎么存就怎么取
3.3、SortedSet接口与TreeSet类
SortedSet接口是Set接口的子接口,除了拥有Set集合的一些基本特点之外,还提供了排序的功能。
TreeSet类就是SortedSet接口的实现类
四、TreeSet
4.1、TreeSet概述
1)TreeSet继承与实现关系
TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, java.io.Serializable接口。
TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。
TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
TreeSet 实现了Cloneable接口,意味着它能被克隆。
TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。
TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator (比较器排序)进行排序。这取决于使用的构造方法。
TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。
另外,TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。
2)存储对象
TreeSet存储对象的时候, 可以排序, 但是需要指定排序的算法。
Integer能排序(有默认顺序), String能排序(有默认顺序), 自定义的类存储的时候出现异常(没有顺序)
如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口
在类上implement Comparable
重写compareTo()方法
在使用TreeSet存储对象的时候, add()方法内部就会自动调用compareTo()方法进行比较, 根据比较结果使用二叉树形式进行存储
3)Tree的构造方法
// 默认构造函数。使用该构造函数,TreeSet中的元素按照自然排序进行排列。
TreeSet() // 创建的TreeSet包含collection
TreeSet(Collection<? extends E> collection) // 指定TreeSet的比较器
TreeSet(Comparator<? super E> comparator) // 创建的TreeSet包含set
TreeSet(SortedSet<E> set)
4)TreeSet与Collection的关系
TreeSet继承于AbstractSet,并且实现了NavigableSet接口。
TreeSet的本质是一个"有序的,并且没有重复元素"的集合,它是通过TreeMap实现的。TreeSet中含有一个"NavigableMap类型的成员变量"m,而m实际上是"TreeMap的实例"。
4.2、TreeSet原理
1)特点
TreeSet是用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列
2)使用方式
2.1)自然顺序(Comparable)
TreeSet类的add()方法中会把存入的对象提升为Comparable类型
调用对象的compareTo()方法和集合中的对象比较
根据compareTo()方法返回的结果进行存储
2.2)比较器顺序(Comparator)
创建TreeSet的时候可以制定 一个Comparator
如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序
add()方法内部会自动调用Comparator接口中compare()方法排序
调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数
2.3)两种方式的区别
TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
TreeSet如果传入Comparator, 就优先按照Comparator
4.3、图解TreeSet的排序原理
4.3.1、环境
我们创建了一个Person类和一个测试类TreeSetDemo_0010类
package com.zyh.domain; public class Person {
private String name;
private int age; public Person(String name,int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} /*
* 为什么是31?
* 1,31是一个质数,质数是能被1和自己本身整除的数
* 2,31这个数既不大也不小
* 3,31这个数好算,2的五次方-1,2向左移动5位
*/
@Override
public int hashCode() {
final int prime = ;
int result = ;
result = prime * result + age;
result = prime * result + ((name == null) ? : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) //调用的对象和传入的对象是同一个对象
return true; //直接返回true
if (obj == null) //传入的对象为null
return false; //返回false
if (getClass() != obj.getClass()) //判断两个对象对应的字节码文件是否是同一个字节码
return false; //如果不是直接返回false
Person other = (Person) obj; //向下转型
if (age != other.age) //调用对象的年龄不等于传入对象的年龄
return false; //返回false
if (name == null) { //调用对象的姓名为null
if (other.name != null) //传入对象的姓名不为null
return false; //返回false
} else if (!name.equals(other.name)) //调用对象的姓名不等于传入对象的姓名
return false; //返回false
return true; //返回true
} @Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Person
package com.zyh.Collection.set; import com.zyh.domain.Person; import java.util.TreeSet; public class TreeSetDemo_0010 {
public static void main(String[] args) {
TreeSet<Person> ts = new TreeSet<>();
ts.add(new Person("张三",));
ts.add(new Person("李四",));
ts.add(new Person("王五",));
ts.add(new Person("赵六",)); for (Person p:ts){
System.out.println(p);
}
} }
TreeSetDemo_0010
当我们直接执行的时候,会报错误。
Exception in thread "main" java.lang.ClassCastException: com.zyh.domain.Person cannot be cast to java.lang.Comparable
分析:Integer能排序(有默认顺序), String能排序(有默认顺序), 自定义的类存储的时候出现异常(没有顺序)
4.3.2、自然排序(Comparable)
我们让Person实现Comparable接口,重写compareTo方法
当我们把返回值设置为1时:
图解:
当返回值是0时,张三作为二叉树的根,当我们其他的元素比较时,都返回0表示相同的对象。所以只会存储张三。
当返回值是-1时,张三作为二叉树的根,李四和它比较时,返回-1说明,李四小,挂在张三的左边。王五一进来也和张三比较返回-1,放在张三左边,在和李四比较返回-1,挂在李四左边,以此类推。
当返回值是1时。和上面一样的推理。
2.1)按照年龄排序
分析:
张三作为二叉树的根,当李四进来的时候,李四的年龄比张三小,挂在张三的左边。当王五进来的时候,王五的年纪比张三大,所以挂在张三的
右边。当赵六的进来的时候,和张三比较年龄结果赵六大,挂在张三的右边,在和王五比较结果比王五小,挂在王五的左边。
结果排序就是:李四、张三、赵六、王五
2.2)按年龄为主要条件,名字是次要条件
4.3.3、比较器顺序(Comparator)
首先我们查看TreeSet的构造方法发现有一个这样的构造方法:
// 指定TreeSet的比较器
TreeSet(Comparator<? super E> comparator)
通过查看它的构造方法就知道可以传入一个比较器。
构造一个新的空TreeSet,它根据指定比较器进行排序。插入到该 set 的所有元素都必须能够由指定比较器进行相互比较:对于 set 中的任意两个元素 e1 和e2,执行 comparator.compare(e1, e2) 都不得抛出 ClassCastException。
如果用户试图将违反此约束的元素添加到 set 中,则 add 调用将抛出 ClassCastException。
1)TreeSetd的Comparetor比较器实现的二种方法
第一种:写个类实现Comparator接口
class myComparator implements Comparator{ @Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2; int num = p1.getName().compareTo(p2.getName());
// 0的话是两个相同,进行下一个属性比较
if (num == ){
return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
} return num;
}
}
然后在new Set的时候放进去。如
TreeSet ts = new TreeSet(new myComparator());
myComparator
第二种:写内名内部类方法
TreeSet ts = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2; int num = p1.getName().compareTo(p2.getName()); if (num == ){
return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
} return num; }
});
JavaSE(八)集合之Set的更多相关文章
- JavaScript数据结构与算法(八) 集合(ECMAScript 6中定义的类似的Set类)
TypeScript方式实现源码 // 特性: // 1. 集合是由一组无序且唯一(即不能重复)的项组成的.这个数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中. // 2. 也 ...
- JavaSE笔记-集合
Java集合大致可分为:List,Set,Map,Queue List:有序,可重复 Set:无序(输出和插入顺序不一定一致),不可重复 Map:映射关系,根据key去访问value Queue:队列 ...
- JavaSE Map集合
Map集合 在Map集合中保存的数据为一组数据,其中:一个数据为key,另外一个数据为value.而key和value具备对应的关系,在集合中它们属于一组(一对)数据.而每个key只能对应唯一的一个v ...
- JavaSE List集合
我们掌握了Collection接口的使用后,再来看看Collection接口中的子接口和实现类,他们都具备那些特性呢? 接下来,我们一起学习Collection中的常用几个子接口: java.ut ...
- JavaSE Set集合
明确Set集合接口的特点. java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collec ...
- JavaSE Collection集合
集合:是java中提供的一种容器,可以用来存储多个对象.可是我们前面学习的数组也是可以保存多个对象的,为什么还要提供集合容器呢?集合和数组它们有啥区别呢? 数组的长度是固定的.一旦创建完成不能改变长度 ...
- Javase之集合体系(4)之Map集合
集合体系之Map集合 ##Map<K,V>( 接口 ) 特点:将键映射到值对象,一个映射不能包含重复的键:每个键只能映射一个值 Map集合与Collection集合的区别 Map集合存 ...
- Javase之集合体系(2)之List及其子类ArrayList,LinkedList与Vector及其迭代器知识
集合体系之List及其子类ArrayList,LinkedList与Vector及其迭代器知识 List(接口) 特点:有序(存储与取出顺序相同),可重复 List子类特点: ArrayList: ...
- Javase之集合体系(3)之Set及其子类知识
集合体系之Set及其子类知识 Set(接口) public interface Set<E>extends Collection<E> 特点:无序(存储顺序与取出顺序不一致 ...
- Javase之集合泛型
集合泛型知识 泛型 是一种把类型明确工作推迟到创建对象或者调用方法的时候才明确的特殊类型. 也称参数化类型,把类型当成参数传递. 在jdk1.5中出现.一般来说经常在集合中使用. 格式 <数据类 ...
随机推荐
- linux系统资源网站
http://upstream.rosalinux.ru/ API/ABI changes analysis for C/C++ libraries
- Linux音频编程指南(转)
转自: http://www.ibm.com/developerworks/cn/linux/l-audio/ Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有 ...
- Oracle PLSQL Demo - 03.流程判断[IF ELEIF ELSE]
declare v_job ) := 'Programmer'; v_sal number; begin if v_job = 'Programmer' then v_sal :; elsif v_j ...
- [转帖]cocos2d-x 3.0rc开发指南:Windows下Android环境搭建
原文请看:http://blog.csdn.net/linzhengqun/article/details/21663341 鲜红字体请注意:文中红色字体乃是本文博主阳光下的蒲公英添加.红色字体部分造 ...
- Solr学习之五
一.段管理 段是一个自包含,仅可读的solr的索引的子集.一旦一个段被刷新到持久存储后,它将不会改变.当添加新文档到你的索引时候,它们被写入到新的段中.因此,在你的索引中,有很多激活的段.一次查询必须 ...
- hive12启动报错org.apache.thrift.server.TThreadPoolServer.<init>(Lorg/apache/thrift/server/TThreadPoolServer$Args;)
执行如下命令启动hive服务:./bin/hive --service hiveserver,报如下错误: Starting Hive Thrift ServerException in thread ...
- 在WEB开发的时候导入各种jar包
使用eclipse导入很简单 右击你的project,选择properties,然后选择java build path,接着选择libraries,点击add external jars即可 如果你还 ...
- ASP.NET(C#)不提示直接关闭当前页面
protected void Button1_Click(object sender, EventArgs e) { //关闭页面--要弹出提示(IE6及以下不弹出提示) ClientScript.R ...
- 一款纯css3实现的漂亮的404页面
之前为大家分享了那些创意有趣的404页面, html5和css3打造一款创意404页面, HTML5可爱的404页面动画很逗的机器人.今天再给大家分享一款纯css3实现的漂亮的404页面.效果图如下: ...
- Session过期后自动跳转到登录页面
最近研究如果用原生的Filter来判别session存在否或者过期否.来跳转到的页面实例,下载来展示代码. 因为顾虑器是每次请求能会进入的,所以可以设置了,进行拦截判断 1.配置web.xml < ...