Set集合

Set简述

特点:不重复,无序(插入和存储顺序不同),只允许存在一个null
Set的实现类有:
HashSet
LinkedHashSet(HashSet的子类):他是有序的!!
TreeSet

1 HashSet

1.1不多比比,先看源码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结论:HashSet是HashMap的一个实例。set的底层就是hashMap,所有的操作都是基于map的操作。

Set接口是一种不包括重复元素的Collection,它维持它自己的内部排序,所以随机访问没有任何意义。

1.2HashSet的存储过程

简述

他的存储方式使他具无序不可重复的特性.
在JDK1.8之前,哈希表底层采用 数组+链表 实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。 但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过8时,将链表转换为红黑树,这样大大减少了查找时间。

哈希表(散列表)原理详解
二叉树简介笔记
扩展:加载因子

扩展:加载因子
HashMap(HashSet)默认的加载因子是0.75,最大容量是16,因此可以得出HashMap的默认容量是:0.75*16=12。所以到容量到12的时候就会扩容。

存储过程:

(1)首先,通过对象的hashCode方法算出对象的哈希码值,它内部采用哈希码对某个数字n进行取余的方式对哈希码进行分组和划分对象的存储区域。(这可以看出HashSet存储跟插入顺序不一样)
(2)如果算出来的存储位置的索引值上有元素对象,这种现象,称为哈希碰撞或者哈希冲突。需要存储的元素对象就与该位置已经存在的对象进行比较,通过equals方法(所以自定义类就要重写hashcode方法和equals方法),判断是否相等,如果相等,不插入。如果不相等,需要在该位置后面插入该元素对象。

存储结构大概如下图:
存储结构
在这里插入图片描述

图摘自:https://blog.csdn.net/qq_41294444/article/details/82106775

1.3HashSet存储的自定义类 去除重复值

HashSet就是采用哈希算法存取对象的集合,它内部用哈希值对某个数字n进行取余的方式对哈希码进行分组和划分对象的存储区域。然后根据哈希码找到相应的存储区域,最后取出该存储区域内的每个元素与该对象进行equals方法比较,这样不用遍历集合中的所有元素就可以的到结论
HashSet去重原理:先走hashCode()分组,跟组员用equals()比较,不同的话直接保存
重写hashCode的原则:
1.相同的对象,一定要有相同的hashCode值(相同hashCode一定在同一存储区域,一定会走equals方法,如果重写了equals,就肯定是相同的,就不保存了
2.不同的对象,尽可能有不同的hashCode值(不同就尽量不在同一内存区域)

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

重写方法解释:这样的话就是判断属性值的hashcode,如果属性值一样,这两个对象一定是一样的,他们的hashcode就必须一样。所以 可以写成return name.hashCode()+age.hashCode(),但这样有可能不一样的属性值的和是一样的呀。
要满足,不同的对象要尽量有不同的hashcode,就需要上边代码的优化了。

重写hashCode()为什么prime是31?

问题:为什么prime是31?
原因一:更少的乘积结果冲突
  31是质子数中一个“不大不小”的存在,如果你使用的是一个如2的较小质数,那么得出的乘积会在一个很小的范围,很容易造成哈希值的冲突。而如果选择一个100以上的质数,得出的哈希值会超出int的最大范围,这两种都不合适。而如果对超过 50,000 个英文单词(由两个不同版本的 Unix 字典合并而成)进行 hash code 运算,并使用常数 31, 33, 37, 39 和 41 作为乘子,每个常数算出的哈希值冲突数都小于7个(国外大神做的测试),那么这几个数就被作为生成hashCode值得备选乘数了。
原因二:31可以被JVM优化
  JVM里最有效的计算方式就是进行位运算了:
左移 << : 左边的最高位丢弃,右边补全0(把 << 左边的数据2的移动次幂)。
右移 >> : 把>>左边的数据/2的移动次幂。
无符号右移 >>> : 无论最高位是0还是1,左边补齐0。   
所以 : 31 * i = (i << 5) - i(左边 31
2=62,右边 2*2^5-2=62) - 两边相等,JVM就可以高效的进行计算啦。。。

总结:hashCode方法决定存储位置;equals方法决定是追加还是覆盖。重写自定义类的hashCode方法时一定要保证散列值相同的情况下,equals方法一定要返回true

2TreeSet

2.1TreeSet简述:

① TreeSet 底层 TreeMap,存储结构是红黑树
②无序,不可重复,可排序

2.2问题

问题1:TreeSet为什么是无序的?

首先,他底层是基于TreeMap的红黑树,不是HashSet的底层数组+链表+红黑树。但他的无序,不可重复,可排序的特点肯定也是因为他的底层存储数据结构。
因为他是可排序的(元素的自然排序),所以存储的位置不一定跟插入顺序一样,所以就导致了他的无序。

问题2:那么他的自然排序是怎么实现的呢?

在这里插入图片描述
在这里插入图片描述
如果直接创建TreeSet集合并添加自定义类并输出,会报 class cannot be cast to java.lang.Comparable

问题3,怎么实现不可重复的?区别hashSet

跟自然排序一样。实现compareTo()或第三方比较器Comparator,只要两个元素比较完是一样的就判定为同一个元素(这就很局限性,只会保存比较结果不同的元素,添加新元素的时候会用compare比较,如果顺序一样(compare方法return 0)就不添加了)。他的去重不用重写hashCode和equals

排序方法1 使用无参构造方法TreeSet()
自定义类实现Comparable接口,并实现compareTo(T o)方法

① 在自定义类实现Comparable 接口 ,可以对该类所对应的集合进行排序,这种排序 称为元素的自然顺序排序。
class Employee implements Comparable<Employee>{.实现compareTo() ..}
② 实现该接口的方法 :compareTo(T o) (ps:实现代码在下边)
1)返回值 大于0 (对象在参数后) 小于0 (对象在参数前 ) 等于0 (相等)
(2)TreeSet集合默认无参构造方法,按照元素的自然顺序进行排序,要求存入该集合的元素自定义类的对象的所在类 必须要实现Comparable,实现接口中的方法:
compareTo方法,否则会报异常 ClassCastException 类型转换异常。
Exception in thread "main" java.lang.ClassCastException Person cannot be cast to java.lang.Comparable,类型转换异常,不能将自定义类转换成Comparable类型。

排序方法2:使用构造方法TreeSet(Comparator<? super E> comparator)
创建Comparator接口的实现类,并实现compare(),传该实现类对象给构造方法

class ComparatorStudents implements Comparator<Students>{...实现compare()...}
实现compare方法跟下边的compareTo是一样的。
TreeSet<Students> ts = new TreeSet<>(new ComparatorStudents());
Comparator : 强行对某个对象集合进行整体排序。
实现compare(T o1, T o2) 方法 。
适用范围 : 当集合中的对象对应的类不能实现Comparable接口的时候,此时,就需要通过第三方比较器来进行比较。

重写compareTo()

重写Comparator 的compare()是类似的。

排序规则:salary高的在前面,salary相同时age大的在前面,age也相同时按照name升序排列。代码如下:

@Override
	public int compareTo(Employee o) {
		// TODO Auto-generated method stub
		// 如果工资相同
		if (this.salary == o.salary) {
			// 如果年龄也相同
			if (this.age == o.age) {
				return this.name.compareTo(o.name);
			}
			return -(this.age - o.age);

		}
		return (int) -(this.salary - o.salary);

	}

注意:

TreeSet的排序和不可重复都是由compareTo( )/compare( )决定的,只要compareTo()返回值是0,就代表两个元素一样(但其实并不是这样的,他的意思是只要排序的位置一样就判定为相同的元素)。
举例说明:

public class TestTreeSet2 {
	public static void main(String[] args) {
		TreeSet<Students> ts = new TreeSet<>(new ComparatorStudents());
		ts.add(new Students("aa", "001", 21));
		ts.add(new Students("aa", "002", 20));
		ts.add(new Students("aa", "004", 21));
		for(Students s:ts) {
			System.out.println(s);
		}
	}
}
class ComparatorStudents implements Comparator<Students>{
	@Override
	public int compare(Students o1, Students o2) {
		return o1.getName().compareTo(o2.getName());
	}
}

输出结果只有:Students [name=aa, sNo=001, age=21]

3LinkedHashSet

HashSet的子类。该集合底层是哈希表与双向链表组成,插入与存储顺序相同。
有序不重复

遍历集合

很简单了呀,增强for遍历万物;set也有Iterator迭代器。
转数组遍历就low了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值