------- android培训、java培训、期待与您交流! ----------
Set
在Collection中,除了List派系之外,还有一个Set派系,
List:元素是有序的(存入和取出的顺序一致),元素可以重复。因为该集合体系有索引。可以在指定位置对元素进行操作。凡是带角标的,底层的实现都是数组数据结构。
Set:元素是无序(存入和取出的顺序不一定一致)的,元素不可以重复。
查看Set接口的功能,才发现Set和Collection是一致的。(增删改查的功能)
在学习Set时候,最重要的是如何才能控制其中的元素唯一。
在Set接口下,有两个我们常用的类比较重要
HashSet:它的底层数据结构是哈希表(保证元素唯一性是判断元素的hashCode和equals方法。)
什么叫做哈希表?
比如,我们定义了两个Demo对象
class Demo
{
}
Demo d1 = newDemo();
Demo d2 = newDemo();
System.out.println(d1);//输出day1.Demo@150bd4d
System.out.println(d2);//输出day1.Demo@1bc4459
如果hashcode的值不一样,那么没有必要计较equals方法了,放到内存中不同的位置
HashSet就是哈希表数据结构的。
例子:
HashSet hs = new HashSet();
hs.add("java01");
hs.add("java02");
hs.add("java03");
hs.add("java03");//返会false。表示为同一个元素
Iterator it = hs.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
//输出 java02 java03 java01
这里我们验证了无序的规律,由于它底层的数据结构式哈希表数据结构(在使用hashCode和equals分别判断了元素是否相等),从而保证了元素具有唯一性。
这里我们来说往hashSet集合中存入自定义对象。
定义一个Person类
class Person
{
private String name;
private int age;
public Person(String name,int age)
{
this.name = name;
this.age = age;
}
@Override
public String toString()
{
return this.name +"...."+this.age;
}
}
并使用下边的例子实现
Person p = new Person("1",1);
Person p2 = new Person("1",1);
Person p1 = new Person("2",2);
HashSet hs = new HashSet();
hs.add(p);
hs.add(p1);
hs.add(p2);
Iterator it = hs.iterator();
while(it.hasNext())
{
Person pp = (Person)it.next();
System.out.println(pp);
}
//输出: 1....1 1....1 2....2
说明相同的人被存入到HashSet中了,不满足HashSet所定义的那样了,必须处理自定义对象,使HashSet中不能存入相同的对象。(满足其原先定义的那样)
也就是必须让相同的对象其hash值相同。建立Person对象的hash值。
对象通过怎么判断相等(如Person中的age,name),就用这些值去生成hash值
对于底层数据结构是哈希表的数据结构的集合,如何保证元素唯一性的呢?
是通过元素的两个方法,hashCode和equals,如果元素的hashCode值相同,才会判断equals是否为true,如果元素的hashCode值不同,不会调用equals。所以不同的对象一般使其hashCode值不同,会更加的高效,不用去调用equels方法去判断是否相等。
遵守的规则:就是当equals相同的时候,hashCode也必须相同,也就是只有相同元素存入的时候,才去调用euqals方法。
这就是哈希结构保证元素唯一性的依据。
当使用这种底层数据结构是哈希表的集合进行删除或者判断某一个元素是否存在。
比如contains和equals,依赖的方法是元素的hashCode和equals方法。
当调用contains(元素)的时候,先将这个元素得到其hashCode的值,通过这个hashCode去分别与集合中其他hashCode去计较,如果不和其他的hashCode一致,则不存在,如果和其中某一hashCode值相同了,则再去调用equals去判断是否相同。从而避免了和所有元素比较,提高了效率。
对于List来说,判断和删除依赖的是equals
而Set来说,先依赖hashCode,然后依赖equals,提高了效率。
TreeSet:可以对Set集合中存储的元素进行排序。
我们用如下的例子体现:
TreeSet:可以对Set集合中存储的元素进行排序。
我们用如下的例子体现:
TreeSet<String>ts= new TreeSet<String>();
ts.add("aaa");
ts.add("ddd");
ts.add("eee");
ts.add("bbb");
for(String s:ts)
{
System.out.println(s);
}
//结果:aaa bbb ddd eee
说明TreeSet可以按存入元素(这里是String)指定的排序方式(自然的顺序)进行排序。这也是它最大的特点。
用TreeSet存储自定义对象
例子:
需求:往TreeSet集合中存储自定义对象学生,想按照学生的年龄进行排序
class Student
{
private String name;
public Student(String name, int age)
{
super();
this.name = name;
this.age = age;
}
private int age;
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public String toString()
{
return this.name+"...."+this.age;
}
}
TreeSet ts = new TreeSet();
ts.add(new Student("01",10));
ts.add(new Student("02",13));
ts.add(new Student("03",11));
ts.add(new Student("04",22));
System.out.println(ts);
报错了,原因是由于TreeSet要对放入其中的元素进行排序,但是按照什么顺序排,它并不知道,所以就报错了,也就是学生对象根本不具备比较性,所以要实现一个接口Comparable(也就是让存入的元素具备了比较性),此接口强行对实现它的每个类的对象进行整体排序,这种排序也称为类的自然排序。
元素实现Comparable接口,就是让元素具备了比较性。
public interface Comparable<T> {
public int compareTo(T o);
}
返回负整数,零,正整数,根据此对象(this)是小于,等于还是大于指定对象(o)。
所以强制让学生具备比较性。
class Student implements Comparable
{
public int compareTo(Object o)
{
Student s = (Student)o;
return this.age - s.age;
}
}
按照其年龄大小进行排序.
在存入元素的过程中,底层不断调用compareTo().
TreeSet ts = new TreeSet();
ts.add(new Student("001",1));
ts.add(new Student("003",2));
ts.add(new Student("002",3));
ts.add(new Student("004",4));
输出:
001compareTo001
003compareTo001
002compareTo001
002compareTo003
004compareTo003
004compareTo002
当存入相同的年龄时候
如:
ts.add(newStudent("004",4));
ts.add(newStudent("004",4));
通过compareTo比较返回0,也就是两个元素相同了,就没有存进去第二个元素对象记住:当用TreeSet进行排序的时候,当主要条件相同时候,一定要判断次要条件,只有当主要和次要都相同时候,这俩个对象才相同。
也就是说TreeSet保证集合中元素唯一性是通过compareTo唯一确定的。当返回0时不再存入元素。
下面学习TreeSet的底层数据结构:
排序无非就是元素直接相互比较,为了避免一个对象要与集合中的所有对象进行比较,底层使用了二叉树的数据结构。如下图的
4存入,
2进来,与4进行比较,小于4,放在左边。
3进来,与4比较,在左,与2比较,大于2,故放在2的右边
1进来,与4比较,在左,与2比较,小于2,故放在2的左边。
也就是底层实现了一个二叉树。从而避免了加入一个元素的时候和集合中的每个元素
进行比较。提高了比较 性能。
但是如果元素越多,都从4这个位置开始走的话,也很慢,所以二叉树到元素越多的时候,会自动取折中值。如下:
ts.add(newStudent("001",4));
ts.add(newStudent("002",3));
ts.add(newStudent("003",2));
ts.add(newStudent("004",1));
输入了:
4compareTo4
3compareTo4
2compareTo4
2compareTo3
1compareTo3 //在这里也就是将3作为一个折中值,从它开始比较,而不是和4进行比较了。
1compareTo2
这样就提高了效率。
那么我们取这个元素怎么取出来的?
默认是从小到大取,也就是按照左中右的方式进行取出。无论你在生成树的时候用哪种比较方式,都是从左往右输入元素。
TreeSet的总结:
可以对Set集合中的元素进行排序。
底层数据结构是二叉树。
保证元素唯一性的依据:compareTo方法 return 0;与hash值没有关系。
如果用TreeSet集合删除元素或者判断元素是否包含,走的都是compareTo(),都得走return 0.去寻找对应的元素。
TreeSet排序的第一种方式:让元素自身具备比较性。元素需要实现Comparable,覆盖compareTo方法。这种方式也称为元素的自然顺序,或者叫做默认顺序。
TreeSet排序的第二种方式:如果元素本身不具备比较性。那么该怎么办?还有一种情况,你本身具备了比较性。但是现在需求变了,想按照其他方式去排,这时候,不能去修改对象本身的排序方式。这时候就需要让集合自身具备比较性。元素交给集合,让集合区分大小顺序。
实现方法:在集合初始化时候,就有了比较方式。
TreeSet(Comparator<?super E> comparator)
构造一个新的空 TreeSet,它根据指定比较器进行排序。
定义一个比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。
class MyCom implements Comparator
{
@Override
public int compare(Object o1, Object o2)//传入两个对象 o1相当于//comparable中的this,o2相当于o
{
Student s1 = (Student)o1;
Student s2 = (Student)o2;
System.out.println(s1.getName()+"..."+s2.getName());
int num = s1.getName().compareTo(s2.getName());
if(num==0)
{
return s1.getAge()-s2.getAge();
}
return num;
}
}
创建集合的时候传入一个比较器实例对象即可,如下
TreeSet ts = new TreeSet(new MyCom());即可
当既有比较器而元素自身又具有比较性的时候,使用比较器的比较方式。都是以return 0;去判断两个元素是否相等。