-----------android培训、java培训、java学习型技术博客、期待与您交流! ------------
一、定义:
Set具有与Collection接口完全一样的接口,因此没有任何额外的功能;其实Set就是Collection,只是行为不同。
二、特点:
无序的(存入和取出的顺序不一定一致),无索引,元素不可重复。
Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。
三、子类:
|--HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效;
|--LinkedHashSet:有序,是hashset的子类。
|--TreeSet:底层的数据结构是二叉树。对Set集合中的元素进行指定顺序的排序,默认是自然顺序。不同步。
四、HashSet
1、保证性元素唯一的原理:
是通过元素的两个方法,hashCode和equals来完成。
如果元素的HashCode值相同,才会继续调用equals方法来判断两元素是否相同:如果为true,那么视为相同元素,不存;如果为false,那么存储。
如果元素的HashCode值不同,不会调用equals,从而提高对象比较的速度。
注意:对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashCode和equals方法。往HashSet里面存的自定义元素一定要复写hashCode和equals方法,以保证元素的唯一性!
2、哈希表的原理:
(1)对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值称为哈希值。
(2)哈希值就是这个元素的位置。要找元素时,先将该元素通过哈希算法算出哈希值,再通过哈希值到哈希表中去查找。
(3)如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。
(4)存储哈希值的结构,我们称为哈希表。
(5)既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。
3、对比:
对于ArrayList集合,判断元素是否存在,或者删元素,底层依据都是equals方法。对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。
4、代码示例:
/*
往hashSet集合中存入自定义对象
姓名和年龄相同为同一个人,重复元素。
*/
import java.util.*;
/*
往哈希表中的存储的自定义对象,必须覆盖hashCode方法,和equals方法。如果不知道这个对象到底存储到哪个容器中去,
那就将hashCode,equals,toString全都覆盖,创建对象自身的判断相同的依据。
*/
class Person {
// 声明属性
private String name;
private int age;
// 构造方法
Person(String name, int age) {
this.name = name;
this.age = age;
}
// getter,setter方法
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;
}
// 覆盖Object类中的hashCode方法。建立Person对象自己特有的哈希值算法。
public int hashCode() {
System.out.println(this.name + "....hashCode");
final int NUMBER = 37;
return name.hashCode() + age * NUMBER;// *37是为了尽量保证哈希值唯一。
}
// 覆盖Object类中的equals方法,建立Person对象。判断是否相同的依据: 根据Person自身的特点来判断。
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
Person p = (Person) obj;
System.out.println(this.name + "...equals.." + p.name);
return this.name.equals(p.name) && this.age == p.age;
}
}
class HashSetTest {
public static void sop(Object obj) {
System.out.println(obj);
}
public static void main(String[] args) {
HashSet<Person> hs = new HashSet<Person>();
// 往哈希表中存储自定义对象。
hs.add(new Person("a1", 11));
hs.add(new Person("a2", 12));
hs.add(new Person("a3", 13));
hs.add(new Person("a2", 12));
hs.add(new Person("a4", 14));
Iterator<Person> it = hs.iterator();
while (it.hasNext()) {
Person p = (Person) it.next();
sop(p.getName() + "::" + p.getAge());
}
}
}
/*output:
a1....hashCode
a2....hashCode
a3....hashCode
a2....hashCode
a2...equals..a2
a4....hashCode
a1::11
a3::13
a2::12
a4::14
*/
五、TreeSet:
1、特点:
底层数据结构式二叉树。可以用于对Set集合进行元素的指定顺序排序。排序需要依据元素自身具备的比较性。线程不同步。
2、保证元素唯一性的方式:
参考Comparable接口的compareTo方法的返回值。也就是参考比较方法的结果是否为0,如果return 0,视为两个对象重复,不存。
3、TreeSet集合排序有两种方式,Comparable和Comparator:
(1)自然排序(默认排序):
1)方法:
让元素自身具备比较性,需要添加的元素对象实现Comparable接口,覆盖compareTo方法。
如果元素不具备比较性,在运行时会发生ClassCastException异常。
所以需要元素实现Comparable接口,强制让元素具备比较性,复写compareTo方法。
依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。
2)注意:
在进行比较时,如果判断条件不唯一,比如,同姓名,同年龄,才视为同一个人。
在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。
3)代码示例:
/*
需求:
往TreeSet集合中存储自定义对象学生。
想按照学生的年龄进行排序。
记住:排序时,当主要条件相同时,一定判断一下次要条件。
*/
import java.util.*;
class Student implements Comparable<Object> {// 该接口强制让学生具备比较性。
private String name;
private int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int compareTo(Object obj) {
if (!(obj instanceof Student))
throw new RuntimeException("不是学生对象");
Student s = (Student) obj;
System.out.println(this.name + "....compareto....." + s.name);
if (this.age > s.age)
return 1;
if (this.age == s.age) {
return this.name.compareTo(s.name);
}
return -1;
}
}
class TreeSetTest1 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>();
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("lisi007", 20));
ts.add(new Student("lisi01", 40));
Iterator<Student> it = ts.iterator();
while (it.hasNext()) {
Student stu = (Student) it.next();
System.out.println(stu.getName() + "..." + stu.getAge());
}
}
}
/*output:
lisi007....compareto.....lisi02
lisi09....compareto.....lisi02
lisi09....compareto.....lisi007
lisi08....compareto.....lisi007
lisi08....compareto.....lisi09
lisi007....compareto.....lisi007
lisi01....compareto.....lisi007
lisi01....compareto.....lisi02
lisi08...19
lisi09...19
lisi007...20
lisi02...22
lisi01...40
*/
(2)比较器:
1)使用条件及方法:
当元素自身不具备比较性(比如存储学生对象时)或者具备的比较性不是我们所需要的比较性时(比如想字符串的长度排序),此时就需要让集合自身具备自定义的比较性。那如何让集合自身具备比较性呢?可在集合初始化时,就让集合自身具备比较性,需要定义一个类(比较器),实现Comparator接口,并覆盖compare方法,将比较器对象作为实际参数传递给TreeSet集合的构造函数。自定义比较器的方法:定义一个类,实现Comparator接口,覆盖compare方法。
注意:第二种方式较为灵活。当两种排序都存在时,以比较器为主。
2)优先考虑比较器的原因:
实现implements Comparable接口定义排序有局限性,实现此接口只能按comparaTo()定义的这一种方法排序,如果同一对象有多种排序方式,应该定义不同的比较器,比如说学生对象可以按学号,分数,年龄等进行多种排序。
3)自定义比较器实质:
让自己编写的类实现Comparator接口,重写Comparator中的比较方法compara(Object a,Objectb)
compara(Object a,Objectb)方法使用:
public int compare(Object a,Object b){
int i = b.对象属性- a.对象属性;
return i; //返回0,表示this==obj ;返回正数表示,this>obj; 返回负数,表示this<obj
}
4)代码示例:
//让学生信息按照学生成绩排序输出
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
/**
*
* 学术类
*
*/
class Student {
/* 声明属性和方法 */
private String name;
private Integer age;
private Integer score;
/* 构造方法 */
public Student(String name, Integer age, Integer score) {
super();
this.name = name;
this.age = age;
this.score = score;
}
/* getter,setter方法 */
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
// 重写String,toString方法
public String toString() {
return "姓名:" + name + " 年龄:" + age + " 成绩:" + score;
}
@Override
// 重写hasCode()方法
public int hashCode() {
return age * name.hashCode();
}
@Override
// 重写equals方法
public boolean equals(Object o) {
Student s = (Student) o;
return age == s.age && name.equals(s.name);
}
}
/**
* TreeSet测试类
*/
/* 创建学生成绩比较器 */
class StudentScoreComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
int i = (int) (o2.getScore() - o1.getScore());
return i;
}
}
public class TreeSetTest2 {
public static void main(String[] args) {
Set<Student> ts = new TreeSet<Student>(new StudentScoreComparator()); // 创建TreeSet集合
Student stu1 = new Student("张三", 18, 85);
Student stu2 = new Student("李四", 19, 88);
Student stu3 = new Student("李红", 20, 60);
Student stu4 = new Student("张丽", 18, 90);
ts.add(stu1); // 将对象放进TreeSet集合中
ts.add(stu2);
ts.add(stu3);
ts.add(stu4);
Iterator<Student> it = ts.iterator(); // 声明迭代器
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
/*output:
姓名:张丽 年龄:18 成绩:90
姓名:李四 年龄:19 成绩:88
姓名:张三 年龄:18 成绩:85
姓名:李红 年龄:20 成绩:60
*/
5)练习:
/*
练习:按照字符串长度排序。
字符串本身具备比较性。但是它的比较方式不是所需要的。
这时就只能使用比较器。
*/
import java.util.*;
class StrLenComparator implements Comparator<Object> {
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));
if (num == 0)
return s1.compareTo(s2);
return num;
}
}
class TreeSetTest {
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<String>(new StrLenComparator());
ts.add("abcd");
ts.add("cc");
ts.add("cba");
ts.add("aaa");
ts.add("z");
ts.add("hahaha");
Iterator<String> it = ts.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
/*output:
z
cc
aaa
cba
abcd
hahaha
*/