目录
一、HashSet
1.1HashSet概述和使用
1.2HashSet存储自定义对象
1.3LinkedHashSet的概述和使用
1.4随机数练习
1.5输入重复字符剔除练习
1.6去除List中重复字符练习
二、TreeSet
2.1TreeSet概述与使用
2.2TreeSet存储自定义对象(按照姓名排序)
2.3TreeSet存储自定义对象(按照姓名长度排序)
2.4使用比较器进行构造TreeSet对象
2.5TreeSet总结
2.6练习
一、HashSet
1.1HashSet概述和使用
Set集合中没有特有的方法,我们只需要关注它的子类如何保证Set的特性:元素唯一不允许重复,
HashSet就是它的一个子类,实现了Set接口,由哈希表支持,
HashSet不保证集合的迭代顺序,有可能会发生改变,并且允许使用null元素。
HashSet<String> hashSet=new HashSet<>();
System.out.println(hashSet.add("a"));
System.out.println(hashSet.add("a"));
System.out.println(hashSet);
我们可以看到,当给哈希集合中存储相同元素时,add方法会返回false值,
最后哈希表中也只会存在一个字符串a,因为Set集合保证了元素的唯一不重复性,
1.2HashSet存储自定义对象
我们在存储自定义对象时,要重写Object类的equals方法和hashCode方法,
否则哈希表中会根据对象的地址来判断对象是否相同,而不是根据对象的属性值是否相同来判断,
import java.util.HashSet;
import java.util.Objects;
public class SetTest {
public static void main(String[] args){
HashSet<Student> hashSet=new HashSet<>();
System.out.println(hashSet.add(new Student("测试",21)));
System.out.println(hashSet.add(new Student("测试",21)));
System.out.println(hashSet);
}
}
class Student{
private String name;
private int age;
public Student(){}
public Student(String name,int age){
this.name=name;
this.age=age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void setName(String name){
this.name=name;
}
public void setAge(int age){
this.age=age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
重写equals方法我们可以理解,因为要比较两个属性值是否相同,但是重写hashcode方法的作用是什么呢?
原来在使用hashset存储元素时,会计算每个元素的哈希地址,
自定义对象计算的默认规则是对象的地址,因为每个对象地址不同,
所以哈希地址也不同,那么存入的时候就直接存进去了,不用比较元素属性值是否相同,
我们重写hashcode方法,让它们计算哈希地址时,根据元素的属性来计算,元素属性相同的计算的哈希地址也相同,
如果哈希地址相同,那么哈希表就会比较一下存储进来的对象和已经在该地址上的对象是否相等,这时就会调用equals方法,
相同那么就不允许相同的对象存入,这样就保证了我们存储自定义对象时元素的唯一性和不重复。
我们再看看它底层是如何计算hashcode的,
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;
}
可以看到,他是遍历属性值,让属性值的hashcode*31,如果属性值为null,那么就为0,
这样算出来的hashcode就不容易重复,也就不会经常调用equals方法来比较,节省了程序的时间,
这个地方取值31主要有以下几点原因,
- 31为一个质数(只能被1和本身整除的数)
- 31这个数既不大也不小
- 31这个数比较好算,为,2向左移动5位再减一
我们最后理一下哈希算法存储元素的步骤:
- 首先HashSet调用add()方法存储对象时,先调用对象的hashCode()方法得到一个哈希值
- 然后在集合中查找是否有哈希值相同的对象
- 如果没有哈希值相同的对象,就直接将对象存入集合
- 如果有哈希值相同的对象,就和哈希值相同的对象逐个进行equals()比较,不同则存入,相同则不存
1.3LinkedHashSet的概述和使用
LinkedHashSet即链表实现的哈希集合,是set集合中唯一可以保证存取顺序的集合,
LinkedHashSet中没有定义自己独特的方法,都是继承的方法,我们只需要知道怎么使用即可,
import java.util.LinkedHashSet;
public class SetTest {
public static void main(String[] args){
LinkedHashSet<String> hashSet=new LinkedHashSet<>();
System.out.println(hashSet.add("a"));
System.out.println(hashSet.add("a"));
hashSet.add("b");
hashSet.add("c");
hashSet.add("d");
System.out.println(hashSet);
}
}
1.4随机数练习
要求我们编写一个程序,获取10个1-20的随机数,要求不能重复,并将结果输出,
Random r=new Random();
HashSet<Integer> hashSet=new HashSet<>();
while(hashSet.size()<10){
hashSet.add(r.nextInt(20)+1);
}
for(Integer integer:hashSet){
System.out.println(integer);
}
1.5输入重复字符剔除练习
要求使用Scanner从键盘读取一行输入,去除其中重复的字符,打印出不同的字符,
Scanner sc=new Scanner(System.in);
System.out.println("请输入一行字符串:");
HashSet<Character> hashSet=new HashSet<>();
String line=sc.nextLine();
char arr[]=line.toCharArray();
for(char c:arr){
hashSet.add(c);
}
for(Character ch:hashSet){
System.out.println(ch);
}
1.6去除List中重复字符练习
要求将List集合中重复的元素去掉,
public static void main(String[] args){
ArrayList<String> list=new ArrayList<>();
list.add("a");
list.add("a");
list.add("b");
list.add("b");
list.add("b");
list.add("b");
list.add("c");
list.add("c");
getSingle(list);
System.out.println(list);
}
public static void getSingle(List<String> list){
LinkedHashSet<String> linkedHashSet=new LinkedHashSet<>();
linkedHashSet.addAll(list);
list.clear();
list.addAll(linkedHashSet);
}
二、TreeSet
2.1TreeSet概述与使用
TreeSet和HashSet不同,该集合可以保证存储的元素处于有序的状态,默认为从小到大,
TreeSet<Integer> treeSet=new TreeSet<>();
System.out.println(treeSet.add(1));
System.out.println(treeSet.add(1));
treeSet.add(3);
treeSet.add(2);
System.out.println(treeSet);
可以看到输出的结果集合中元素是有序排列的,并且保证了元素的唯一不重复,
TreeSet底层为二叉树,小的存储在左边(负数),大的存储在右边(正数),相等就不存,
在TreeSet集合中如何存储元素取决于compareTo()方法的返回值。
2.2TreeSet存储自定义对象(按照姓名排序)
我们在用TreeSet存储自定义对象时,必须要实现Comparable的接口,重写里面的compareTo()方法,
public class Student implements Comparable<Student>{
...
@Override
public int compareTo(Student student){
int num=this.name.compareTo(student.name);
return num == 0 ? this.age-student.age : num;
}
}
我们这里定义好了比较的规则,也就是按照姓名来进行排序,然后我们测试一下看看是否正确,
TreeSet<Student> treeSet=new TreeSet<>();
treeSet.add(new Student("测试1",22));
treeSet.add(new Student("测试2",21));
treeSet.add(new Student("测试3",20));
System.out.println(treeSet);
2.3TreeSet存储自定义对象(按照姓名长度排序)
如果要按照姓名长度进行排序,则需要修改compareTo()方法即可,
@Override
public int compareTo(Student student){
int length=this.name.length()-student.name.length();
int num = length == 0 ? this.name.compareTo(student.name) : length;
return num == 0 ? this.age-student.age : num;
}
我们来测试一下结果,
public static void main(String []args){
TreeSet<Student> treeSet=new TreeSet<>();
treeSet.add(new Student("测试人员1",22));
treeSet.add(new Student("测试2",21));
treeSet.add(new Student("测试人3",20));
System.out.println(treeSet);
}
2.4使用比较器进行构造TreeSet对象
现在如果我们需要将字符串按照长度进行排序,而String底层的compareTo()方法是按照字典顺序来排序的,
也就是按照a到z的顺序来排序,这个时候我们可以给定TreeSet一个比较器Comparator,让集合按照这个比较器的规则来进行排序,
这个Comparator是一个接口,我们需要先定义一个类实现这个接口中的compare方法,定义字符串按照长度排序的规则,
import java.util.Comparator;
public class CompareByLength implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
int num=s1.length()-s2.length();
return num == 0 ? s1.compareTo(s2) : num;
}
}
然后我们将定义好的类CompareByLength作为构造参数传入TreeSet的构造函数,测试一下,
public static void main(String []args){
TreeSet<String> treeSet=new TreeSet<>(new CompareByLength());
treeSet.add("aaaa");
treeSet.add("z");
treeSet.add("dd");
System.out.println(treeSet);
}
2.5TreeSet总结
TreeSet是用来排序的集合,可以指定顺序,对象存入之后按照指定的顺序进行排列,
其使用方式有以下几种:
- 自然顺序(Comparable)
- TreeSet的类add()方法会把存入的对象提升为Comparable类型
- 然后调用对象的compareTo()方法和集合中的对象进行比较
- 根据compareTo()方法返回的结果进行存储
- 比较器顺序(Comparator)
- 创建TreeSet的时候可以指定一个Comparator
- 如果传入了Comparator的子类对象,那么TreeSet就会按照比较器中的顺序排序
- add()方法内部会自动调用Comparator接口中的compare()方法排序
- 调用的对象是compare()方法中的第一个参数,集合中的对象是compare()方法的第二个参数
两种方式有以下两点区别:
- TreeSet构造函数什么都不传,默认按照Comparable的顺序,没有就报错ClassCastException
- TreeSet如果传入Comparator,就有限按照Comparator中的compare()方法来排序
2.6练习
假设在一个集合中存储了无序并且重复的字符串,现在要求我们定义一个方法,
让其按照字典顺序有序,并且不能够去除重复的字符,
public static void main(String []args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("a");
list.add("d");
list.add("c");
list.add("c");
list.add("c");
list.add("b");
sort(list);
System.out.println(list);
}
public static void sort(ArrayList<String> list) {
//排序元素并保留重复
TreeSet<String> treeSet=new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
int num=s1.compareTo(s2);
return num == 0 ? 1 : num;//保证元素重复时也可以存储
}
});
treeSet.addAll(list);
list.clear();
list.addAll(treeSet);
}