一、 Set集合概述及特点
因为Set集合的API方法和Collection集合一模一样。故没有没有它的特殊的方法。
所以我们只要学它的两个子类,一个HashSet和另外一个TreeSet
二、 HashSet存储字符串并遍历
实现Set接口,由哈希表(实际是一个hashmap对象)支持,它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。此类允许下使用null元素。
案例:
HashSet<String> hashSet =new HashSet<String>();
boolean b1 = hashSet.add("a");
boolean b2= hashSet.add("a");
boolean b3= hashSet.add("b");
System.out.println(b1);
System.out.println(b2);
System.out.println(b3);
System.out.println(hashSet);
//HashSet的继承体系中有重写toString方法
for (String string : hashSet) {
System.out.println(string);
//只要使用那个迭代器迭代,那么就可以使用foreach循环
}
效果如下:
tips:Set集合无索引,不可以重复,无序(存储不一致)它的继承系统中有toString方法,故不需要书写toString()方法。
三、 HashSet存储自定义对象
存储自定义对象,并保证元素唯一性。
例:
注意因为set集合不保证元素的有序性,所以每次存储都是无序的
HashSet<Person> hashSet =new HashSet<Person>();
hashSet.add(new Person("张三",12));
hashSet.add(new Person("张三",12));
hashSet.add(new Person("李四",13));
hashSet.add(new Person("李四",13));
hashSet.add(new Person("李四",13));
System.out.println(hashSet);
效果如下:
当我们重写equals方法,使我们出现的姓名和年龄相同就表示存储人物相同。但是在Set集合中我们并不能用equals方法进行判断。而是先要设置hasCode的值。
@Override
public boolean equals(Object obj) {
Person person =(Person)obj;
return this.name.equals(person.name) &&this.age ==person.age;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return 10;
}
当hasCode返回值相同时,说明我们内存中的存储的某类对象会处在相同位置。然后才会调用equals方法,使用equas方法进行判断如果我们的姓名和年龄相同则返回true,不进行对象存储。如果返回false,则会采用筒状式,将对象存储起来。如图所示:
但是为了保证程序的效率,我们尽可能的保证hasCode不相同,然后再执行equals方法。当然如果属性值相同的化,那么它的hasCode的值必然是相同的。属性不相同的返回值尽可能不同。Eclipse等编译器可以自动帮我们生成代码块。快捷键:ctrl+shifts +h 即可生成。并且我们勾选判断的属性。画图如下:
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
注意该处为什么prime是31?
好处:31是一个质数,质数是能被1和自己本身整除的数。并且31不大也不小。
四、 HashSet如何保证元素唯一性的原理
1.HashSet原理(重点)
- 我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数
- 当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象
- 如果没有哈希值相同的对象就直接存入集合
- 如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存
2.将自定义类的对象存入HashSet去重复
- 类中必须重写hashCode()和equals()方法
- hashCode(): 属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)
- equals(): 属性相同返回true, 属性不同返回false,返回false的时候存储。
五、 LinkedHashSet的概述和使用
LinkedHashSet概述:
- 它的底层是链表实现的,是set集合中唯一一个能保证怎么存就怎么取的集合。(因为它是链表实现,可以记录前后继的地址值。)
- 它是HashSet的子类,所以可以保证元素是唯一的,与HashSet原理一样。
LinkedHashSet的特点:可以保证怎么存就怎么取。意思就是你是怎么放进去的就可以怎么取出来
LinkedHashSet<String> linkedHashSet =new LinkedHashSet<String>();
linkedHashSet.add("a");
linkedHashSet.add("a");
linkedHashSet.add("a");
linkedHashSet.add("b");
linkedHashSet.add("b");
linkedHashSet.add("c");
linkedHashSet.add("d");
System.out.println(linkedHashSet);
效果如下:
六、 TreeSet存储Integer类型的元素并遍历
TreeSet是用来对元素进行排序的,并且它也是不存储相同数据的。
TreeSet<Integer> treeSet =new TreeSet<Integer>();
treeSet.add(1);
treeSet.add(1);
treeSet.add(3);
treeSet.add(3);
treeSet.add(2);
treeSet.add(2);
treeSet.add(4);
System.out.println(treeSet);
效果如下:
七、 TreeSet存储自定义对象
当我们单纯的去存储对象的时候,会进行报错。因为对象不能用来做比较。所以我们需要给我们bean类添加接口方法。
public class Person implements Comparable<Person>{
private String name;
private int 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;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
return 1;
}
}
TreeSet部分
TreeSet<Person> treeSet =new TreeSet<Person>();
treeSet.add(new Person("张三",12));
treeSet.add(new Person("李四",12));
treeSet.add(new Person("王五",13));
treeSet.add(new Person("赵六",14));
System.out.println(treeSet);
效果如下:
注意:CompareTo方法的返回值:
如果return返回的数是正数
,集合就是怎么存怎么取
。
如果return返回的数是负数
,集合就是倒序存储
。
如果return返回的数是0
,集合中就存在一元素
。
八、 TreeSet保证元素唯一和自然排序的原理
当我们按照年龄进行排序
书写Bean包中的Person实体类
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
return this.age -o.age;
}
TreeSet<Person> treeSet =new TreeSet<Person>();
treeSet.add(new Person("张三",12));
treeSet.add(new Person("李四",22));
treeSet.add(new Person("周七",24));
treeSet.add(new Person("王五",10));
treeSet.add(new Person("赵六",24));
System.out.println(treeSet);
原理如下:
效果如下:
此时我们会发现,少一个赵六的元素,很简单,因为我们只进行了年龄判断,所以后面存储的年龄相同的元素不会被存储进来。
如果我们进行升级:按照年龄进行排序
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
int num =this.age -o.age;
return num == 0? this.name.compareTo(o.name):num;
}
该方法会进行完整的排序。
九、TreeSet存储自定义对象并遍历练习1(按照姓名排序)
@Override
public int compareTo(Person o) {
int num = this.name.compareTo(o.name);
//按照姓名排序
return num== 0? this.age -o.age :num;
//年龄是次要条件
}
TreeSet<Person> treeSet =new TreeSet<Person>();
treeSet.add(new Person("李四",22));
treeSet.add(new Person("张三",12));
treeSet.add(new Person("周七",24));
treeSet.add(new Person("王五",10));
treeSet.add(new Person("赵六",24));
System.out.println(treeSet);
效果如下:
十、 TreeSet存储自定义对象并遍历练习2(按照姓名的长度排序)
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
int length =this.name.length() - o.name.length();
//比较姓名长度
int num =length == 0 ?this.name.compareTo(o.name):length;
//完全有一种可能,名字长度一样,内容是不一样的
return num==0? this.age -o.age:num;
//完全有一种可能,姓名和长度均相同
}
十一、 TreeSet保证元素唯一和比较器排序的原理
TreeSet(Collection <? super E> comparator)
:构造一个新的空TreeSet,他根据指定比较器进行排序
需求:将字符串按照长度排序
TreeSet<String> treeSet =new TreeSet<String>(new CompareByLen());
//Comparator c = new CompareByLen();
treeSet.add("aaaaaaaaaa");
treeSet.add("z");
treeSet.add("wc");
treeSet.add("nba");
treeSet.add("cba");
System.out.println(treeSet);
新建一个类
class CompareByLen implements Comparator<String>{
@Override
public int compare(String s1, String s2) {
//按照字符串的长度比较
// TODO Auto-generated method stub
int num = s1.length() -s2.length();
//长度为主要条件
return num==0? s1.compareTo(s2): num ;
//内容为次要条件
}
}
效果如下:
原理如下:
依次从左边就先取出,如果没有的话就取出其右边。
十二、 TreeSet原理
1.特点
TreeSet是用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列
2.使用方式
a.自然顺序(Comparable)
- TreeSet类的add()方法中会把存入的对象提升为Comparable类型
- 调用对象的compareTo()方法和集合中的对象比较
- 根据compareTo()方法返回的结果进行存储
b.比较器顺序(Comparator)
- 创建TreeSet的时候可以制定 一个Comparator
- 如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序
- add()方法内部会自动调用Comparator接口中compare()方法排序
- 调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数
c.两种方式的区别
- TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
- TreeSet如果传入Comparator, 就优先按照Comparator