一、 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