Java集合之Set超详细讲解

Set集合

  • Set集合中的对象不按特定的方式排序,只是简单的将对象加入到集合中,但是Set集合不能包括重复对象
  • Set接口继承了Collection接口,因此也包含Collection接口的所有方法

1.哈希(hash)前序:

1.1哈希表:

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

在这里插入图片描述

从上图中可以看出,哈希表实则是数组+链表的形式组成,数组指的上图中的01233456…,链表则指的是在这里插入图片描述

2 . Hashset集合

HashSet扩展自AbstractSet并且实现Set接口。
在这里插入图片描述

2.1 HashSet集合特点:

import java.util.HashSet;

class Test{
	public static void main(String []args) {
		HashSet<Integer> hs = new HashSet<>();
		
		hs.add(5);
		hs.add(2);
		hs.add(3);
		System.out.println(hs.add(4));
		System.out.println(hs.add(4));  //检验HashSet是否允许重复元素加入
		System.out.println(hs);    //检验HashSet是否有序
			
	}
}

Ouput:

true
false
[2, 3, 4, 5]

说明HashSet不允许重复,无序

2.2 放入自定义类型数据出现的问题


public class Student{	
	
	private String name;
	private int id;
	
	public Student(){}
	
	public Student(String name, int id)
	{
		super();
		this.name = name;
		this.id = id;
	}

	public String getName()
	{
		return name;
	}
	public void setName(String name)
	{
		this.name = name;
	}
	public int getId()
	{
		return id;
	}
	public void setId(int id)
	{
		this.id = id;
	}

	@Override
	public String toString()
	{
		return "Student [name=" + name + ", id=" + id + "]";
	}	
	
}

import java.util.HashSet;

class Test{
	public static void main(String []args) {
		HashSet<Student> hs = new HashSet<>();
		
		hs.add(new Student("yiyi",1));
		hs.add(new Student("feifei",2));
		hs.add(new Student("lili",3));

		System.out.println(hs.add(new Student("wawa",4)));	
		System.out.println(hs.add(new Student("wawa",4)));
		System.out.println(hs);
			
	}
}

Output:

true
true
[Student [name=yiyi, id=1], Student [name=wawa, id=4], Student [name=wawa, id=4], Student [name=lili, id=3], Student [name=feifei, id=2]]

问题:为什么是true,true。放入了两个一样的对象,为什么依然显示true,并且出现了出现了两次 Student [name=wawa, id=4], Student [name=wawa, id=4]。与上述HashSet不允许重复的特点相异。

为了解决这一问题,我们从底层原理说起

以int数据类型为例,假设我们要在hashSet中放入12,5,7,12,9,首先通过hashcode()方法计算它们的哈希值,根据哈希取模运算后(y=x%5),依次对应为2,0,2,2,4。 然后依次放入哈希表中,在放入时,有重复的数值比如2,则需要另外的方法equals()来判断是否相等,如果相等(重复)就不再放入,否则放入。
在存入int,string等类型时,Java帮我们写了hashcode()和equals()方法,所以才能显现出hashset的特点,无序单一性,我们存入自己定义的数据类型时,系统并没有相对于我们自定义数据类型的hashcode()和equals()方法。

因此,我们要想往hashSet里存入数据,就只能重写hashcode()和equals()方法!!

如何重写equals()和hashCode()方法

在这里插入图片描述
右键–>Source–>Generate hashCode() and equals()即可


public class Student{	
	
	private String name;
	private int id;
	
	public Student(){}
	
	public Student(String name, int id)
	{
		super();
		this.name = name;
		this.id = id;
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (id != other.id)
			return false;
		if (name == null)
		{
			if (other.name != null)
				return false;
		}
		else if (!name.equals(other.name))
			return false;
		return true;
	}

	public String getName()
	{
		return name;
	}
	public void setName(String name)
	{
		this.name = name;
	}
	public int getId()
	{
		return id;
	}
	public void setId(int id)
	{
		this.id = id;
	}

	@Override
	public String toString()
	{
		return "Student [name=" + name + ", id=" + id + "]";
	}	
	
}

import java.util.HashSet;
import java.util.Iterator;



class Test{
	public static void main(String []args) {
		HashSet<Student> hs = new HashSet<>();
		
		hs.add(new Student("yiyi",1));
		hs.add(new Student("feifei",2));
		hs.add(new Student("lili",3));

		System.out.println(hs.add(new Student("wawa",4)));	
		System.out.println(hs.add(new Student("wawa",4)));
		System.out.println(hs);  //第一种打印方式,利用重写的toString()方法和Println()直接打印
		System.out.println("------------");
		for(Student s : hs) {   //第二种打印方式,增强for循环
			System.out.println(s);
		}
		System.out.println("------------");   //第三种打印方式,利用Iteator
		Iterator<Student> it = hs.iterator();
			while(it.hasNext()) {
				Student s = it.next();
				System.out.println(s);
			}
		
		
			
	}
}

Output:

true
false
[Student [name=feifei, id=2], Student [name=wawa, id=4], Student [name=yiyi, id=1], Student [name=lili, id=3]]
------------
Student [name=feifei, id=2]
Student [name=wawa, id=4]
Student [name=yiyi, id=1]
Student [name=lili, id=3]
------------
Student [name=feifei, id=2]
Student [name=wawa, id=4]
Student [name=yiyi, id=1]
Student [name=lili, id=3]


3.LinkedHashSet集合

在这里插入图片描述
LinkedHashSet继承自HashSet
特点是:有序,唯一,效率高

4.TreeSet集合

TreeSet为使用树来进行储存的Set接口提供了一个工具,对象按升序储存,访问和检索是很快的。在存储了大量的需要进行快速检索的排序信息的情况下,TreeSet是一个不错的选择。
在这里插入图片描述
在这里插入图片描述

import java.util.Set;
import java.util.TreeSet;

class Test{
	public static void main(String []args) {
		Set<Integer> st = new TreeSet<>();
		st.add(1);
		st.add(4);
		st.add(5);
		st.add(2);
		System.out.println(st.add(2)); //验证是否可以添加重复元素
		System.out.println(st);		
	}
}

Output:

false
[1, 2, 4, 5]
  • false说明TreeSet集合不允许重复
  • [1, 2, 4, 5]说明,TreeSet对Integer数据类型进行升序排列
import java.util.Set;
import java.util.TreeSet;

class Test{
	public static void main(String []args) {
		Set<String> st = new TreeSet<>();
		st.add("java");
		st.add("c");
		st.add("pyhthon");
		st.add("c++");
		System.out.println(st.add("c++"));
		System.out.println(st);		
	}
}

Output:

false
[c, c++, java, pyhthon]
  • false说明TreeSet集合不允许重复
  • [c, c++, java, pyhthon]说明,TreeSet对String数据类型进行升序排列

说到按升序排列,肯定会想到内部比较器和外部比较器(如果不了解Java比较器可参考主页文章(https://blog.csdn.net/weixin_44551646/article/details/94741936)),事实上,Java在String和Integer类里重写了comparaTo方法,因此TreeSet可以对其进行升序排列。
在这里插入图片描述
在这里插入图片描述
那我们对自定义的TreeSet进行排序时,就需要自己重写比较方法。如果说没有重写任何比较器(内部或者外部)时,使用TreeSet进行操作会报错。

4.1使用内部比较器与TreeSet



public class Student implements Comparable{	
	
	 String name;
	 int age;
	 
	public Student(){}
	
	public Student(String name, int age)
	{
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public int compareTo(Object o)
	{
		Student stu =((Student)o);
		return this.age-stu.age;  //按照年龄升序排序
	}

	@Override
	public String toString()
	{
		return "Student [name=" + name + ", age=" + age + "]";
	}
}


import java.util.Set;
import java.util.TreeSet;

class Test{
	public static void main(String []args) {
		Set<Student> st = new TreeSet<>();
		st.add(new Student("lili",18));
		st.add(new Student("nana",19));
		st.add(new Student("huahua",20));
		st.add(new Student("baba",10));
		
		System.out.println(st);		
	}
}

Output:

[Student [name=baba, age=10], Student [name=lili, age=18], Student [name=nana, age=19], Student [name=huahua, age=20]]

我们看的Output中的对象年龄按照从小到大的方式排列。

4.2使用外部比较器与TreeSet

import java.util.Comparator;

public class Student {	
	
	 String name;
	 int age;
	 
	public Student(){}
	
	public Student(String name, int age)
	{
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString()
	{
		return "Student [name=" + name + ", age=" + age + "]";
	}


}

class OutsideCompare implements Comparator{

	@Override
	public int compare(Object o1, Object o2)
	{
		Student st1 = ((Student) o1);
		Student st2 = ((Student) o2);
		return st1.age-st2.age;
	}
	
}

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

class Test{
	public static void main(String []args) {
		
		OutsideCompare com = new OutsideCompare();
		
		Set<Student> st = new TreeSet<>(com);
		st.add(new Student("lili",18));
		st.add(new Student("nana",19));
		st.add(new Student("huahua",20));
		st.add(new Student("baba",10));
		
		System.out.println(st);		
	}
}

注意⚠️:

  • 使用外部比较器时,在测试类里要创建外部比较器对象OutsideCompare com = new OutsideCompare(); 也可以用多态的形式Comparator com =new OutsideCompare();如果这里有疑问,强烈推荐去看主页文章,关于内部比较器和外部比较器(https://blog.csdn.net/weixin_44551646/article/details/94741936)
  • 我们需要把创建出的外部比较器对象com传入TreeSet中,Set<Student> st = new TreeSet<>(com);即括号里的com。
    因为通过TreeSet源码可知在这里插入图片描述
    TreeSet需要传入Comparator当作参数!!
  • 5
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值