HashSet集合中元素的添加过程
- 我们想要清楚HashSet中的中的元素是如何存储的,这个时候我们就先要清楚HashSet底层是通过怎样一个结构来存储数据的
HashSet底层的存储
- HashSet底层是通过数组来实现的,只不过HashSet底层不仅仅是使用了数组进行存储,而是通过使用数组加链表的方式进行存储的
- 在我们的jdk7.0之中:我们一旦使用空构造方法创建一个HashSet对象之后立马会在内存中开辟一个长度为16的数组(当然在我们的实际编程之中是可以指定创建长度的)
- 这个就类似与我们的单例设计模式中的饿汉式
- 在我们的jdk8.0之中:我们使用无参构造方法只是创建了一个空的数组(也就是创建了一个{},空数组的长度为0),当我们使用add()方法时才会创建一个长度为16的数组
- 我们jdk8中的这个存储方式可以让我们的程序执行的效率更加高
- 这个就类似与我们的单例设计模式中的懒汉式
- 在我们的jdk7.0之中:我们一旦使用空构造方法创建一个HashSet对象之后立马会在内存中开辟一个长度为16的数组(当然在我们的实际编程之中是可以指定创建长度的)
我们的HashSet中的这个底层存储在jdk7到jdk8中的变化就和我们的ArrayList是一样的,jdk7的时候都是一上来就创建默认长度的数组(ArrayList中底层在jdk7中创建的默认数组长度为10),直到jdk8中都是在使用add()方法的时候才创建默认长度的数组
我们要在HashSet中添加元素,我们肯定还要知道我们的HashSet中存储的数据的特点:
- 这里我们说我们得到HashSet中存储的对象要是无序的,还要是不可重复的
那么我们如何去使用我们底层的数组加链表来实现我们添加的对象是无序的呐?我们又如何去保证我们添加的对象是不可重复的?
添加元素的过程:
我们向HashSet中添加元素a,首先会调用元素a所在类的hashCode()方法,计算元素a的hash值(哈希值),之后再根据得到的元素a的hash值再通过散列函数(一种算法,我们可以通过这个算法得到我们元素a在数组中应该存储的索引位置)计算出在我们的元素a在底层数组中的存放位置(也就是索引位置),再判断这个位置上是否已经有了元素了
- 如果此位置上没有其他元素,则元素a添加成功 ---- 情况一
- 如果此位置上有其他元素b(或者以链表形式存在的多个元素),则比较元素a与元素b的hash值
- 如果元素a和元素b的哈希值不相同,则元素a添加成功 ---- 情况二
- 如果元素a和元素b的哈希值相同,进而需要调用元素a所在类的equals()方法判断
- 如果equals()方法返回false,则元素a添加成功 ---- 情况三
- 如果equals()方法返回true,则元素a添加失败
上面我们的情况一,情况二,情况三是我们的元素添加成功的三种情况
- 对于情况二和情况三而言,我们的元素a存储时我们的元素a存储的位置上原本就已经有元素了,这个时候我们的元素a是和已经存在的指定索引上的数据以链表的方式进行存储
- 那么以链表方式进行存储到底是如何进行存储的,是如何链的?
- jdk7中将元素a放到数组中,然后让元素a指向原来的元素
- jdk8中原理的元素值数组中,然后指向我们的元素a
- 但是这里有一个共同点:都是从数组中的元素指向数组外的元素
- 我们可以将这样的存储方式归纳为七上八下
- 也就是在jdk7中我们的新添加的元素a在上,也就是在数组中,然后指向下面的元素
- 在jdk8中我们新添加的元素a在下,也就是我们的原本的元素在数组中,这个时候是我们的原本的元素指向我们新添加的元素
散列函数:
- 上面我们提到了这个名词,我们的散列函数就是我们要通过这个函数将我们的hash值转化为数组中要存储的索引位置
- 我们就可以将散列函数看做是一个算法就可以
易错点避免:
-
两个不同的对象,这两个不同的对象的hash值有可能是相同的
两个相同的对象,这两个对象的hash值就一定是相同的
- 这个就说明我们在HashSet中添加元素时即使我们的元素的索引位置相同,并且hash值也相同,也有可能不是同一对象,这个时候我们还要通过使用equals()方法判断是否是真正的同一个对象
-
不同的两个hash值通过散列函数计算的数组的索引位置可能相同
- 也就是当我们的HashSet中底层的数组中的同一索引位置中的元素可能是不同的hash值的对象,这个时候我们就要在判断一下同一位置上的元素的hash值是否相同
我们的HashSet什么时候会进行扩容:
- 当HashSet底层的数组的使用率超过了0.75就会进行扩容,底层数组就会扩为原来的2倍
- HashSet底层默认创建的数组长度为16,16*0.75=12,也就是我们的底层数组使用超过12个时我们的的HashSet底层就会扩容,第一次扩容之后的容量就为32
总结:
在我们Set中添加元素的时候要在添加元素的模板类中重写hashCode()方法和equals()方法
- 但是其实我们的TreeSet中添加对象的时候是通过comparable中的接口中的compareTo()方法的返回值或者是通过Comparator接口中的compare()方法的返回值判断元素是否重复的