一、Set接口的层级结构
1、特点
2、层级关系
二、HashSet
1、数据结构:哈希表
2、保证元素唯一性
3、HashSet添加、删除、包含判断依据
三、TreeSet
1、TreeSet的介绍
2、TreeSet存储自定义对象
3、数据结构:二叉树(红黑树)
4、比较器
5、保证元素唯一性
一、Lis接口的层级结构
1、特点
Set是无序,不可重复的集合。Set集合与Collection基本相似,它没有提供额外的方法,可以说Set就是一个Collection 。
注意: 无序指的是:元素的存入和取出的顺序不一定一致
2、层级结构
Collection
|-- Set : 元素时无序的,元素不可以重复。
|-- HashSet: 底层数据结构是哈希表,线程是非同步的。
| --TreeSet: 底层数据结构是二叉树,可以对Set集合中的元素进行排序
二、HashSet
1、数据结构: 哈希表
Hash是一种数据结构,用户查找对象。Hash为每一个对象计算出一个整数,称为Hash Code(哈希值)。哈希表是按照哈希值来存的。当我们添加元素时,哈希值是一样的,这时候,会进行是否是同一对象判断equals,如果不是同一对象,那么会在当前对象下顺延(串下来的)。
哈希表是一个链接式列表的阵列。每一个列表称为一个buckets(哈希表元)。对象位置的计算index=HashCode%buckets。
如何查看哈希值?
Demo d1 = new Demo();
System.out.println(obj);
输出: Demo@15db9742
为什么是输出Demo@15db9742
这样的格式?
我们看下Object中的public String toString()
方法的实现
Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())
所以,15db9742 就是哈希值。
1、无序不重复
public static void main(String[] args) {
HashSet hs = new HashSet();
sop(hs.add("java1")); // true
sop(hs.add("java1")); //false
hs.add("java2");
Iterator it = hs.iterator();
while(it.hasNext()){
sop(it.next());
}
}
public static void sop(Object obj){
System.out.println(obj);
}
运行结果
true
false
java2
java1
哈希值一样,同一对象。添加失败。存入和取出顺序不一致。
2.保证元素唯一性
HashSet是如何保证元素的唯一性呢?
是通过元素的两个方法,hashCode和equals方法来完成 的。
先计算哈希值。
( 1 ) 如果元素的hashCode值不同,不会调用equals,添加成功
( 2 ) 如果元素的hashCode值相同,才会判断equals是否为true,为true添加失败
所以,当我们自定义对象的时候,一般要复写hashcode和equals方法,因为自定义对象可能要存放到hashSet集合中。还有,复写hashCode要尽量保证哈希值的唯一性,一般根据判断条件生成。
注意: 复写的原则。
下面看一例子: hashSet存自定义对象Person,当姓名和年龄一致,元素重复。
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Person("a1", 11));
hs.add(new Person("a2", 22));
hs.add(new Person("a3", 33));
hs.add(new Person("a3", 33));
Iterator it = hs.iterator();
while(it.hasNext()){
Personperson = (Person)it.next();
sop(person.getName()+"::"+person.getAge());
}
//重写了equals,没重写hashcode方法。发现equals发现没被调用,而且都存入成功。说明计算得到是不同的哈希值。
// 鉴于需求,我们需要去重。说明,我们需要覆盖hashcode方法,建立自己的哈希值。
// 哈希值的生成根据判断条件
// 重写hashcode方法,发现,去重成功了。而且equals方法运行了。
}
class Person{
private String name;
private int age;
public Person(String n,int a){
this.name = n;
this.age = a;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public boolean equals(Object obj){
if(!(obj instanceof Person))
return false;
Personp = (Person)obj;
System.out.println(this.name+"...."+this.age);
return this.name.equals(p.getName()) && this.age == p.getAge();
}
public int hashCode()
{
System.out.println(this.name+"...hashcode");
//return 60; 会有很多重复比较
return this.name.hashCode()+this.age*13;
}
3、HashSet添加、删除、包含判断依据
添加前面说了。
hs.remove(new Person("a1", 11));
hs.contains(new Person("a2", 12));
删除:先计算对象new Person("a1", 11)
的哈希值,如果哈希值不存在,删除失败。如果哈希值存在,调用equals方法,找到对象并删除对象。
包含 :先计算对象的哈希值。如果哈希值存在,调用equals方法,返回结果。如果不存在,直接返回false。
总结 :HashSet对于判断元素是否存在以及删除等操作依赖的方法是元素的hashcode和equals方法。同理,ArrayList判断元素是否存在以及删除等操作,只依赖元素的equals方法。原因其实也很简单,跟数据结构有关。数据结构不同,依赖的方法不一样。
三、TreeSet
1、TreeSet的介绍
TreeSet是SortedSet接口的唯一实现,可以确保集合元素处于排序状态。TreeSet支持两种排序方式:自然排序和定制排序,默认情况下采用自然排序。
下面看一例子:
public static void main(String[] args) {
TreeSet ts = new TreeSet();
ts.add("dsa");
ts.add("abc");
ts.add("a");
Iterator it = ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
输出结果:
a
abc
dsa
变换上面add方法的添加顺序,你会发现,输出结果始终不变。
分析:Java提供了一个Compare接口,该接口定义了一个compareTo(Object obj)
方法,该方法返回一个整数值。实现该接口的类就必须实现该方法,实现了该接口的对象就可以比较大小。换句话说,无论你是自定义对象还是系统定义对象,你想放进TreeSet集合,你就必须实现Compareable接口中的compareTo(Object obj)
方法。上述集合添加的元素为String对象。String就实现了Compareable接口。总归来说: TreeSet要排序,就要让元素自身具备比较性。元素需要实现Comparable接口,覆盖CompareTo方法。
大部分类在实现compareTo(Object obj)
方法,都需要将比较对象obj强制转化成相同类型,因为只有相同类的两个实例才能比较大小。当试图把一个对象添加到TreeSet中,TreeSet会调用该对象的compareTo(Object obj)
方法与集合中其他元素进行比较。如果不是同一类元素,会发生ClassCastException(类型转换)异常。当一个对象调用另一个对象进行比较时,例如obj1.compareTo(obj2)
,如果该方法放回0,则表明两个对象相等,如果该方法返回一个正整数,表明obj1大于obj2,如果返回一个负整数,表明obj1小于obj2.
2、TreeSet存储自定义对象
需求: 往TreeSet集合中存自定义对象学生,想按照学生的年龄进行排序。
分析: 年龄一样,姓名不同的情况要考虑!!!
public static void main(String[] args) {
TreeSet ts = new TreeSet();
ts.add(new Student("a1",11));
ts.add(new Student("a2",12));
ts.add(new Student("a3",13));
ts.add(new Student("a2",13));
Iterator it = ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
// TrteeSet中的元素要具备比较性,需要实现Comparable接口,实现compareTo方法
class Student implements Comparable
{
private String name;
private int age;
public Student(String n,int a){
this.name = n;
this.age = a;
}
public int compareTo(Student obj){
/*
if(this.age > obj.age){
return 1;
//如果年龄一样,不继续判断姓名。同年龄不同姓名的元素就存不进去了。
}else(this.age == obj.age){
return this.name.compareTo(obj.name);
}
return -1;
*/
// 下面是优化后的代码
int num = new Integer(this.age).compareTo(new Integer(obj.age));
if(num == 0){
return this.name.compareTo(obj.name);
}
return num;
}
public String toString(){
return this.name+"::"+this.age;
}
}
注意: 排序时,当主要条件相同时,一定要判断次要条件。
3、数据结构:二叉树
既然TreeSet是有序的,那么如何确定元素的位置呢?
public static void main(String[] args) {
TreeSet ts = new TreeSet();
ts.add(new Student("lisi02",22));
ts.add(new Student("lisi007",20));
ts.add(new Student("lisi09",19));
ts.add(new Student("lisi08",19));
ts.add(new Student("lisi11",40));
ts.add(new Student("lisi16",30));
ts.add(new Student("lisi10",29));
ts.add(new Student("lisi22",90));
ts.add(new Student("lisi007",20));
Iterator it = ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
输出结果如下:
li09::19
lisi08::19
li007::20
lisi007::20
lisi02::22
lisi10::29
lisi16::30
lisi11::40
lisi22::90
分析:从中我们可以看出,当对象ts.add(new Student("lisi11",40));
被添加时候,现与对象new Student("lisi12",20)
对比,发现年龄比22大。左边的都不用对比了。节省了比较次数,提高了性能。二叉树到元素一多以后,会自动去折中值先进行比较,从中间向两边散发。
注意 :在TreeSet集合比较对象,它只看Compare结果。 如果,你始终将CompareTo返回值为1,意思就是怎么存进去,怎么取出来;返回-1,就是逆序;返回0,只存进一个元素。
4、比较器 compator
如果一个类不能用于实现 java.lang.Comparable
或者不喜欢默认的comparable行为(改写系统对象),提供自己的排序顺序,我们可以实现Comparator接口来定义一个比较器。
TreeSet的构造方法有如下:
我举一个例子: 字符串根据长度排序。字符串自己实现了Comparable接口,但是并不满足我们的需求。因此,我们需要自定义比较器。
//自定义比较器
class StrLengthCompare implements Comparator
{
public int compare(String obj1,String obj2){
int num = new Integer(obj1.length()).compareTo(new Integer(obj2.length()));
if(num == 0){
return obj1.compareTo(obj2);
}
return num;
}
}
//main方法如下
public static void main(String[] args) {
TreeSet ts = new TreeSet(new StrLengthCompare());
ts.add("abcd");
ts.add("cc");
ts.add("cba");
ts.add("z");
ts.add("helloofa");
Iterator it = ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
5、保证元素唯一性
保证元素唯一性的一句:conpareTo方法return0。
注意:
无论是comparable接口的compareTo方法,还是自定义类去实现compator接口的compare方法,当返回值为0的时候,仅仅是表示两个对象在同一位置。对于TreeSet而言,判断两个对象不相等的标准是两个对象通过equals方法比较返回false或者通过compareTo(object obj)比较没有返回为0.即使两个对象为同一对象,TreeSet也会把他当成两个对象处理。