集合源码学习2:Set 源码学习

首图

一、HashSet 源码

1、作者前言

image-20210420094728569

从作者前言来看,我们就可以归纳HashSet的要点了:

  • 实现Set接⼝
  • 不保证迭代顺序
  • 允许元素为null
  • 底层实际上是⼀个HashMap实例
  • 非同步
  • 初始容量⾮常影响迭代性能

2、基本属性

image-20210420094953203

3、构造函数

image-20210420095852269

image-20210420100101497

4、添加元素

image-20210420100434965

value是⼀个Object,所有的value都是它。

所以可以直接总结出:HashSet实际上就是封装了HashMap,操作HashSet元素实际上就是操作HashMap。

下面我们来看看Map的put方法

image-20210420101119622

看看如何计算key的hash

image-20210420101455204

从而发现hash并不是直接使用key的hashCode()函数返回的int值,还需要进行位异或运算

补充:位异或运算(^)

运算规则是:两个数转为二进制,然后从高位开始比较,如果相同则为0,不相同则为1。

比如:8^11

8转为二进制是1000,11转为二进制是1011.从高位开始比较得到的是:0011.然后二进制转为十进制,就Integer.parseInt("0011",2)=3;

image-20210420121009068

  1. HashSet底层是HashMap
  2. 添加一个元素时,先得到hash值,然后再与size-1进行与运算,得出索引值
  3. 找到存储数据表table,看这个索引位置是否已经存放的有元素
  4. 如果没有,就直接加入
  5. 如果有,说明索引值一样,判断hash值是否一样,再调用==和equals比较,如果相同就放弃添加,如果不相同,则添加到链表的尾部
  6. 在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)

下面我们来看看HashMap是如何扩容的,主要分为2部分:

1、数组扩容

image-20210420140608694

2、将旧表上的数据复制到新表上

image-20210420142201106

看看是然后转换为红黑树的

image-20210420143519143

总结:触发扩容的时机:

  1. 数组中数据的size达到阙值,指的并不是每个数组的索引位置,而是添加元素的个数。
  2. 当链表的长度达到TREEIFY_THRESHOLD = 8会触发树化,但如果数组的长度小于MIN_TREEIFY_CAPACITY = 64并不会树化,而是会进行扩容。

二、LinkedHashSet 源码

1、作者前言

image-20210420163851408

从作者前言来看,我们就可以归纳LinkedHashSet的要点了:

  • 迭代是有序的
  • 允许为null
  • 底层实际上是⼀个LinkedHashMap实例(其实就是数组+双向链表)
  • 非同步
  • 性能比HashSet差⼀丢丢,因为要维护⼀个双向链表
  • 初始容量与迭代速度无关,LinkedHashSet迭代的是双向链表

2、构造函数

image-20210420165337970

父类构造函数

image-20210420165706702

3、添加元素

LinkedHashSet没有实现add()方法,所以调用父类HashSet的add()方法,而HashSet是调用底层数据结构map的put()方法。

public boolean add(E e) {
    return map.put(e, PRESENT)==null;   // 这里的map是构造函数创建的LinkedHashMap
}

因为底层创建的是LinkedHashMap,但LinkedHashMap并没有实现put()方法,所以调用的还是父类HashMap的put方法进行添加元素。只不过底层保存数据的类型不同,HashMap使用的是Node结点,而LinkedHashMap使用的是Entry(继承Node类增加了before和after属性用来实现双向链表)

image-20210421101615930

添加方法:

image-20210421102720008

LinkedHashMap重写了newNode()方法,从而实现在插入数据的时候实现前后指针

image-20210421103213443

下面我们来看看这个双向链表是如何实现的~

image-20210421103533821

可以看到这个双向链表的实现非常简单,就只是创建一个新Entry结点的时候直接链接在尾部,从而达到读取顺序一致。除此之外,LinkedHashSet 跟 HashSet 几乎一样。

image-20210421105342297

三、TreeSet 源码

1、成员属性

image-20210422164736732

2、构造函数

image-20210422164902379

3、常用方法

1、添加元素,调用的是TreeMap的put方法

public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}

2、删除元素,调用的是TreeMap的remove方法

public boolean remove(Object o) {
    return m.remove(o)==PRESENT;
}

下面不再过多阐述,具体细节查看TreeMap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值