【学习笔记】集合(三)
Set 集合
-
特点:无序、无下标、元素不能重复
-
方法:全部继承自Collection中的方法,本身没有定义方法
Set 接口的使用
-
包括数组的创建,添加、删除、遍历、判断操作
package com.collection.set.setDemo;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo01 {
public static void main(String[] args) {
//创建数组
Set<String> set = new HashSet<>();
//添加元素,没有顺序,并且重复添加,加不进去
set.add("小米");
set.add("华为");
set.add("iphone");
set.add("小米");
System.out.println(set);
//删除元素,只能通过对象删除
// set.remove("iphone");
// set.clear();
//遍历集合,不能使用普通的for循环
System.out.println("-----forEach循环------");
for (String str:
set) {
System.out.println(set);
}
System.out.println("-----Iterator迭代器------");
Iterator<String> it = set.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
//判断
System.out.println("集合中是否包含华为:"+ set.contains("华为"));
System.out.println("集合是否为空:" + set.isEmpty());
}
}
Set 实现类
HashSet
-
HashSet:
-
基于HashCode 值 计算元素存放位置
-
当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者cunru
-
存储结构是 哈希表(数组 + 链表 + 红黑树)
-
就像车站售票窗口一样,窗口表示数组,排队的人表示链表
-
-
HashSet 的方法与 Set 一致
-
存储过程(也是元素重复的依据)
-
根据hashCode计算保存的位置(在数组),如果此位置为空,则直接保存,如果不为空执行第二步
-
执行equals方法,如果equals方法为true,则认为重复,否则,形成链表,也就是在数组那个位置上已经有的那个元素指向新的元素,形成链表
-
-
我们先创建一个person的集合,并添加元素
package com.collection.set.hashSet;
import java.util.HashSet;
public class Demo01 {
public static void main(String[] args) {
//创建hashSet集合
HashSet<Person> persons = new HashSet<>();
//添加元素
Person p1 = new Person("张三",20);
Person p2 = new Person("李四",26);
Person p3 = new Person("王五",23);
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(new Person("张三",20));
System.out.println(persons);
//删除元素
}
}
我们把已经创建好的对象加入到集合中是可以加入的,但是如果我们加入一个属性相同的对象,它也能加入。
如果 我们想让属性相同的对象不能加入集合中,就要重写hashCode 方法和equals方法
首先通过hashcode 判断它们的属性是否相同,如果相同则返回相同的哈希值,然后在通过equals判断它们是否为同一个对象,它们的属性是否相等
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
再去添加属性相同的对象,就添加不进去了
删除方法也是相同的道理
//删除元素
persons.remove(new Person("张三",20));
System.out.println(persons);
判断是否存在
//判断是否存在
System.out.println("张三是否存在:"+persons.contains(new Person("张三", 20)));
如果只重写equals ,相同属性的对象依旧可以添加到集合中,
原因就是,不重写hashCode 方法,新new 的对象 和与它属性相同的对象 的 哈希值有可能不同,那么,新new的对象的哈希值所对应的数组有可能是空的,那么新new 的对象可以直接放到这个数组里,就不去调用equals 了。
补充:
在重写hashCode 方法时,源码如下
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
对于为什么要用 31 去乘以 result,有两点原因:
-
31 是一个质数,可以减少散列冲突,也就是尽量让计算的哈希值不同
-
提高执行效率,因为 31 * i = (i << 5) - i 可以算的更快 比如 31 * 2 = (2<<5)- 2
TreeSet
-
存储结构:红黑树
-
基于排列顺序实现元素不重复
-
实现了SortedSet 接口,对集合元素自动排序
-
元素对象的类型必须实现Comparable 接口,指定排序规则
-
通过CompareTo方法确定是否为重复元素
红黑树:二叉查找树,一个节点最多有两个子节点。而且左边的节点都比右边的节点小。比如说我们先添加一个10,那么这个10就是根节点,我们在加一个 7 ,7比10小,所以放在10的左边,在加一个15,这个15应该连在10右边,再加一个 8 ,这个8 应该连在7右边,如果再添加一个7 ,查找后发现有7 了,那么认为7 重复了,就不再添加了
红黑树要求根节点必须是黑的 ,有的节点是黑的,有的是红的,为了保持平衡,不至于导致一边元素特别多,一边特别少
TreeSet 的特点与Set相同,只是在添加元素的时候,打印出来时,是按照字母表进行排序的
package com.collection.set.treeSet;
import java.util.TreeSet;
public class Demo01 {
public static void main(String[] args) {
TreeSet<String> tree = new TreeSet<>();
tree.add("abc");
tree.add("xyz");
tree.add("hello");
tree.add("xxxx");
System.out.println(tree);
}
}
遍历时,顺序相同
下面我们通过保存Person数据,来说明TreeSet 是如何排序的
package com.collection.set.treeSet;
import com.collection.set.hashSet.Person;
import java.util.TreeSet;
public class Demo02 {
public static void main(String[] args) {
TreeSet<Person> persons = new TreeSet<>();
Person p1 = new Person("张三",20);
Person p2 = new Person("李四",23);
Person p3 = new Person("王五",18);
persons.add(p1);
persons.add(p2);
persons.add(p3);
System.out.println(persons.size());
}
}
运行时出了异常
类型转换异常:Person类型不能转换成Comparable
原因是:在红黑树中,插入节点时,要进行比较,当我们插入Person类型的对象时,我们没有为它定义比较的规则,所以程序并不知道要比较什么,所以就会报异常
现在我们为其定义比较的规则:要求是元素必须要实现Comparable接口,在Comparable的抽象方法中去实现比较规则:先按姓名比,在按年龄比
package com.collection.set.hashSet;
import java.util.Objects;
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = 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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public int compareTo(Person o) {
int n1 = this.name.compareTo(o.getName());
int n2 = this.age - o.getAge();
return n1 == 0 ? n2 : n1;
}
}
再去添加Person类型的数据,就可以加进来了
删除时,通过new 对象的形式去删除
persons.remove(new Person("王五",16));
可以删除,原因是比较的是姓名和年龄,当我们 new 的这个对象的姓名和年龄与之前的重复时,就可以删除了。
contains 也是一样。
补充:Comparator
Comparator可以实现定制比较 ,它是一个比较器,我们在添加元素时可以不用实现Comparable 接口。在创建集合时,指定比较规则。
Comparator是接口,不能直接new ,我们使用匿名内部类的方式创建
package com.collection.set.treeSet;
import com.collection.set.hashSet.Person;
import java.util.Comparator;
import java.util.TreeSet;
public class Demo03 {
public static void main(String[] args) {
TreeSet<Person> persons = new TreeSet(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
//先比较年龄,在比较姓名
int n1 = o1.getAge() - o2.getAge();
int n2 = o1.getName().compareTo(o2.getName());
return n1 == 0 ? n2 : n1;
}
});
Person p1 = new Person("张三",20);
Person p2 = new Person("李四",23);
Person p3 = new Person("王五",18);
Person p4 = new Person("王五",16);
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(p4);
System.out.println(persons);
}
}
案例:
使用TreeSet 集合实现字符串按照长度排序
package com.collection.set.treeSet;
import java.util.Comparator;
import java.util.TreeSet;
public class Demo04 {
public static void main(String[] args) {
TreeSet<String> tree = new TreeSet(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int n1 = o1.length() - o2.length();
int n2 = o1.compareTo(o2);
return n1 == 0 ? n2 : n1;
}
});
tree.add("helloworld");
tree.add("zhangsan");
tree.add("lisi");
tree.add("wangwu");
tree.add("xian");
tree.add("bingjing");
System.out.println(tree);
}
}