本帖最后由 yijincheng 于 2016-3-22 22:27 编辑
今天的学习内容是集合的Set结构
相比于Link结构,Set的特点是无序(存储和读取的顺序是不一致的) ,无索引,不可以重复存储相同元素。
Ste集合的这个帮派中,有HashSet和TreeSet两大马仔。两个各司其职,各有擅长的地方。
类 hashSet <.E>
这个类的特点是:
不保证迭代顺序;
也不保证顺序不变(上次输出结果和下次输出结果未必一样,即使没有对容器进行任何操作和修改);
但是能够保证元素不重复。(这恐怕就是hashSet的看家本领了)
hashSet这个唯一赖以生存的本领的实现原理是什么呢?
首先就是写在的名字上的hash。
1.HashSet原理
* 我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数
* 当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象
* 如果没有哈希值相同的对象就直接存入集合
* 如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存
2.将自定义类的对象存入HashSet去重复
类中必须重写hashCode()和equals()方法
hashCode(): 属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)
equals(): 属性相同返回true, 属性不同返
在向集合中添加元素时,内在的逻辑是先比较元素的hashCode,当hashCode一样时,再用equals方法比较。
Hashcode是方法hashCode()生成的很有可能是元素独有的标识。
- @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;
- }
- PS:这里的prime的值为何是31?一方面是因为它是个不大不小的质数,这样算出来的hashCode更容易独一无二。另一方面,31是2^5-1,系统得到这个数比较快。
- @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;
- }
复制代码
Tips:eclipse中,在自定义类中,shift + Alt + s 再按h ,可以自动生成hashCode和equals方法
LinkedHashSet
特点: 可以保证怎么存就怎么取
在操作上,LinkedHashSet没什么特殊之处。和其他集合的操作相同。
TreeSet
首先,TreeSet是一个用来对元素排序的的集合。这个功能很强大,以后就不用自己写那麻烦的双循环嵌套的排序算法了。
自定义类必须实现Comparable接口,才能成功被添加进TreeSet集合中。
实现Comparable接口就要重写CompareTo方法。当对象被添加进集合中时,集合会调用该对象的CompareTo方法和集合内的元素进行比较。
CompateTo 会返回一个int类型。
TreeSet的数据结构是二叉树。每个节点都有两个引用,分别指向下一级的左节点、右节点。
CompareTo的返回值是负数时,新元素存数在左边;是正数时,存放在右边;等于0时不存储。
读取的顺序是左中右。这样就实现了对元素的排序。
当然,自定义类的CompareTo你也可以重写,按实际要求做出修改。例如当要求允许重复元素时,就要把返回值0的情况归到不为零的判断中。
TreeSet的比较器排序。
比较器需要自己定义。因为只是在实例化TreeSet时才调用一次比较器,所以可以用匿名内部类的方式来定义。
例如:
- TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {//定义比较器(new Comparator(){}是Comparator的子类对象)
-
- @Override
- public int compare(String s1, String s2) { //重写compare方法
- int num = s1.compareTo(s2); //比较内容
- return num == 0 ? 1 : num; //如果内容一样返回一个不为0的数字即可
- }
- });
复制代码
两种排序方式的总结:
* 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
|
|